From d1b824f3c2a7a7b3e37e70f336e5a1580028c63e Mon Sep 17 00:00:00 2001
From: Maurice Raybaud <mauriceraybaud@hotmail.fr>
Date: Mon, 25 Apr 2022 14:38:30 +0200
Subject: [PATCH] POV: fix some nested code and further files structure cleanup

* 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_povray/__init__.py                     |  399 ++--
 render_povray/base_ui.py                      |  305 ---
 render_povray/icons/pov.add.box.dat           |  Bin 4346 -> 4346 bytes
 render_povray/icons/pov.add.cylinder.dat      |  Bin 5858 -> 5876 bytes
 .../icons/pov.add.infinite_plane.dat          |  Bin 8090 -> 9278 bytes
 render_povray/icons/pov.add.loft.dat          |  Bin 17810 -> 16622 bytes
 ...circle.dat => pov.add.polygontocircle.dat} |  Bin 5192 -> 6344 bytes
 render_povray/icons/pov.add.prism.dat         |  Bin 4382 -> 4382 bytes
 render_povray/icons/pov.add.rainbow.dat       |  Bin 9422 -> 9422 bytes
 render_povray/icons/pov.add.sphere.dat        |  Bin 11402 -> 11348 bytes
 render_povray/icons/pov.add.spheresweep.dat   |  Bin 9062 -> 9152 bytes
 render_povray/model_all.py                    |  915 ++++++++
 render_povray/model_curve_topology.py         |  996 +++++++++
 render_povray/{object_gui.py => model_gui.py} |  546 ++---
 render_povray/model_meta_topology.py          |  306 +++
 render_povray/model_poly_topology.py          |  719 ++++++
 render_povray/model_primitives.py             |  796 +++++++
 ...itives.py => model_primitives_topology.py} | 1109 ++-------
 ...ject_properties.py => model_properties.py} |   18 +-
 render_povray/nodes.py                        | 1056 +++++++++
 render_povray/nodes_fn.py                     |  704 ++++++
 render_povray/nodes_gui.py                    |  278 +++
 render_povray/nodes_properties.py             |  703 ++++++
 render_povray/object_curve_topology.py        |  971 --------
 render_povray/object_mesh_topology.py         | 1534 -------------
 .../{object_particles.py => particles.py}     |  187 +-
 render_povray/particles_properties.py         |  718 ++++++
 render_povray/render.py                       | 1349 ++---------
 render_povray/render_core.py                  |  784 +++++++
 render_povray/render_gui.py                   |  250 ++-
 render_povray/render_properties.py            |   17 +-
 render_povray/scenography.py                  |  688 +++---
 render_povray/scenography_gui.py              |  112 +-
 render_povray/scripting.py                    |  746 +++---
 render_povray/scripting_gui.py                |   53 +-
 render_povray/scripting_properties.py         |    4 +-
 render_povray/shading.py                      | 1663 ++------------
 render_povray/shading_gui.py                  |  130 +-
 render_povray/shading_nodes.py                | 1992 -----------------
 render_povray/shading_properties.py           | 1350 +----------
 render_povray/shading_ray_properties.py       |  374 ++++
 render_povray/texturing.py                    |  623 +++---
 render_povray/texturing_gui.py                |  282 ++-
 render_povray/texturing_procedural.py         |  694 ++++++
 render_povray/texturing_properties.py         |   91 +-
 render_povray/ui_core.py                      |  280 +++
 render_povray/update_files.py                 |   32 +-
 .../{df3_library.py => voxel_lib.py}          |    0
 48 files changed, 12073 insertions(+), 11701 deletions(-)
 delete mode 100755 render_povray/base_ui.py
 rename render_povray/icons/{pov.add.polytocircle.dat => pov.add.polygontocircle.dat} (69%)
 create mode 100644 render_povray/model_all.py
 create mode 100644 render_povray/model_curve_topology.py
 rename render_povray/{object_gui.py => model_gui.py} (74%)
 mode change 100755 => 100644
 create mode 100644 render_povray/model_meta_topology.py
 create mode 100644 render_povray/model_poly_topology.py
 create mode 100644 render_povray/model_primitives.py
 rename render_povray/{object_primitives.py => model_primitives_topology.py} (51%)
 mode change 100755 => 100644
 rename render_povray/{object_properties.py => model_properties.py} (97%)
 mode change 100755 => 100644
 create mode 100644 render_povray/nodes.py
 create mode 100644 render_povray/nodes_fn.py
 create mode 100644 render_povray/nodes_gui.py
 create mode 100644 render_povray/nodes_properties.py
 delete mode 100755 render_povray/object_curve_topology.py
 delete mode 100755 render_povray/object_mesh_topology.py
 rename render_povray/{object_particles.py => particles.py} (61%)
 mode change 100755 => 100644
 create mode 100644 render_povray/particles_properties.py
 create mode 100644 render_povray/render_core.py
 delete mode 100755 render_povray/shading_nodes.py
 create mode 100644 render_povray/shading_ray_properties.py
 create mode 100644 render_povray/texturing_procedural.py
 create mode 100644 render_povray/ui_core.py
 rename render_povray/{df3_library.py => voxel_lib.py} (100%)
 mode change 100755 => 100644

diff --git a/render_povray/__init__.py b/render_povray/__init__.py
index 3d5e53156..fb7ccb287 100755
--- a/render_povray/__init__.py
+++ b/render_povray/__init__.py
@@ -4,75 +4,139 @@
 
 """Import, export and render to POV engines.
 
-These engines can be POV-Ray or Uberpov but others too, since POV is a
-Scene Description Language. The script has been split in as few files
-as possible :
+These engines can be POV-Ray, Uberpov, HgPovray but others too, since POV is a
+Scene Description Language. The script has been split in as few files as possible and
+metaphorically structured as a train with some boilerplate rendering locomotive,
+followed by its cars each representing a thematic in the 3D field:
+
+########################################### RENDERING ###########################################
 
 __init__.py :
-    Initialize properties
+    Provide script's metadata, Initialize addon preferences, (re)load package modules
+
+ui_core.py :
+    Set up workspaces and load other ui files for the user to set up all variables
+
+render_core.py :
+    Define the POV render engines declinations from generic Blender RenderEngine class
+
+render_properties.py :
+    Initialize properties for render parameters (Blender generic and POV native)
+
+render_gui.py :
+    Display properties from render_properties.py for user to change them
+
+render.py :
+    Translate render properties (Blender and POV native) to POV, ini file and bash
 
-base_ui.py :
-    Provide property buttons for the user to set up the variables
+                                                                          __------------------Z__
+                                                                    _--¨¨] |  __ __ _____________||
+                                                                _-¨7____/  | | °|° | □□□ □□□ □□□ ||
+                                                               (===========|=|  |  |=============||
+                                                                `-(@)@)--------------------(@)(@)-'
+############################################# LAYOUT ##############################################
 
 scenography_properties.py
-    Initialize properties for translating Blender cam/light/environment parameters to pov
+    Initialize properties for passing layout (camera/light/environment) parameters to pov
 
 scenography_gui.py :
-    Display cam/light/environment properties from scenography_properties.py for user to change them
+    Display camera/light/environment properties from scenography_properties.py for user to change
 
 scenography.py
-    Translate  cam/light/environment properties to corresponding pov features
+    Translate  camera/light/environment properties to corresponding pov features
 
-object_properties.py :
-    nitialize properties for translating Blender objects parameters to pov
+                                                    __------------------Z__   ____________________
+                                              _--¨¨] |  __ __ _____________|||____________________|
+                                          _-¨7____/  | | °|° | □□□ □□□ □□□ ||| □□□ □□□ □□□ □□□ □□ |
+                                         (===========|=|  |  |=============|||====================|
+                                          `-(@)@)--------------------(@)(@)-^-(@)(@)--------(@)(@)-
+############################################### MODEL #############################################
 
-object_primitives.py :
-    Display some POV native primitives in 3D view for input and output
+model_properties.py :
+    Initialize properties for translating Blender geometry objects parameters to pov
 
-object_mesh_topology.py :
-    Translate to POV the meshes geometries
+model_gui.py :
+    Display properties from model_properties.py for the user to change them
 
-object_curve_topology.py :
+model_all.py :
+    Translate to POV the object level data
+
+model_poly_topology.py :
+    Translate to POV the mesh based geometries
+
+model_curve_topology.py :
     Translate to POV the curve based geometries
 
-object_particles.py :
-    Translate to POV the particle based geometries
+model_meta_topology.py :
+    Translate to POV the metaball based geometries
+
+model_primitives.py :
+    Display some simple POV native primitives in 3D view for input and output
+
+model_primitives_topology.py :
+    Display some POV native complex or compound primitives in 3D view for input and output
 
-object_gui.py :
-    Display properties from object_properties.py for user to change them
+                             __------------------Z__   ____________________   ____________________
+                       _--¨¨] |  __ __ _____________|||____________________|||____________________
+                   _-¨7____/  | | °|° | □□□ □□□ □□□ ||| □□□ □□□ □□□ □□□ □□ ||| □□□ □□□ □□□ □□□ □□
+                  (===========|=|  |  |=============|||====================|||====================
+                   `-(@)@)--------------------(@)(@)-^-(@)(@)--------(@)(@)-^-(@)(@)--------(@)(@)
+############################################ SHADING #############################################
 
 shading_properties.py :
     Initialize properties for translating Blender materials parameters to pov
 
-shading_nodes.py :
-    Translate node trees to the pov file
+shading_ray_properties.py :
+    Initialize properties for translating Blender ray paths relevant material parameters to pov
 
 shading_gui.py :
-    Display properties from shading_properties.py for user to change them
+    Display variables from shading_properties.py and shading_ray_properties.py for user to change
 
 shading.py
     Translate shading properties to declared textures at the top of a pov file
 
 texturing_properties.py :
-    Initialize properties for translating Blender materials /world... texture influences to pov
+    Initialize properties for translating Blender materials/world... texture influences to pov
 
 texturing_gui.py :
-    Display properties from texturing_properties.py for user to change them
+    Display properties from texturing_properties.py available for user to change
 
 texturing.py :
-    Translate blender texture influences into POV
+    Translate blender pixel based bitmap texture influences into POV
 
-render_properties.py :
-    Initialize properties for render parameters (Blender and POV native)
+texturing_procedural.py :
+    Translate blender algorithmic procedural texture influences into POV
 
-render_gui.py :
-    Display properties from render_properties.py for user to change them
+           __------------------Z__   ____________________   ____________________   ________________
+     _--¨¨] |  __ __ _____________|||____________________|||____________________|||________________
+ _-¨7____/  | | °|° | □□□ □□□ □□□ ||| □□□ □□□ □□□ □□□ □□ ||| □□□ □□□ □□□ □□□ □□ ||| □□□ □□□ □□□ □□□
+(===========|=|  |  |=============|||====================|||====================|||================
+ `-(@)@)--------------------(@)(@)-^-(@)(@)--------(@)(@)-^-(@)(@)--------(@)(@)-^-(@)(@)----------
+############################################ VFX/TECH #############################################
 
-render.py :
-    Translate render properties (Blender and POV native) to POV, ini file and bash
+particles_properties.py :
+    Initialize all strands, fx, or particles based objects properties to be translated to POV
+
+particles.py :
+    Translate to POV the particle based geometries
+
+voxel_lib.py :
+    Render smoke to *.df3 files using an updated version of Mike Kost's df3.py legacy library
+
+nodes_properties.py :
+    Define all node items and their respective variables or sockets available to POV node trees
+
+nodes.py :
+    Translate node trees to the pov file
+
+nodes_fn.py :
+    Functions toolbox used by nodes.py to translate node trees to the pov file
+
+nodes_gui.py :
+    Operators and menus to interact with POV specific node trees
 
 scripting_properties.py :
-    Initialize properties for scene description language parameters (POV native)
+    Initialize properties for hand written scene description language fragments (POV native)
 
 scripting_gui.py :
     Display properties from scripting_properties.py for user to add his custom POV code
@@ -80,15 +144,13 @@ scripting_gui.py :
 scripting.py :
     Insert POV native scene description elements into blender scene or to exported POV file
 
-df3_library.py :
-    Render smoke to *.df3 files
-
 update_files.py :
     Update new variables to values from older API. This file needs an update
 
+######################################## PRESETS/TEMPLATES ########################################
 
 Along these essential files also coexist a few additional libraries to help make
-Blender stand up to other POV IDEs such as povwin or QTPOV
+Blender stand up to other POV enabled IDEs (povwin, POV for Mac, QTPOV, VSCode, Vim...)
     presets :
         Material (sss)
             apple.py ; chicken.py ; cream.py ; Ketchup.py ; marble.py ;
@@ -135,7 +197,7 @@ Blender stand up to other POV IDEs such as povwin or QTPOV
 
 
 bl_info = {
-    'name': "Persistence of Vision",
+    'name': "POV@Ble",
     'author': "Campbell Barton, "
               "Maurice Raybaud, "
               "Leonid Desyatkov, "
@@ -143,10 +205,10 @@ bl_info = {
               "Constantin Rahn, "
               "Silvio Falcinelli,"
               "Paco GarcĂ­a",
-              "version": (0, 1, 2),
-    'blender': (2, 81, 0),
+    'version': (0, 1, 3),
+    'blender': (3, 2, 0),
     'location': "Render Properties > Render Engine > Persistence of Vision",
-    'description': "Persistence of Vision integration for blender",
+    'description': "Persistence of Vision addon for Blender",
     'doc_url': "{BLENDER_MANUAL_URL}/addons/render/povray.html",
     'category': "Render",
     'warning': "Co-maintainers welcome",
@@ -161,25 +223,39 @@ bl_info = {
 if "bpy" in locals():
     import importlib
 
-    importlib.reload(base_ui)
-    importlib.reload(shading_nodes)
+    importlib.reload(ui_core)
+    importlib.reload(nodes_properties)
+    importlib.reload(nodes_gui)
+    importlib.reload(nodes_fn)
+    importlib.reload(nodes)
     importlib.reload(scenography_properties)
+    importlib.reload(scenography_gui)
     importlib.reload(scenography)
     importlib.reload(render_properties)
     importlib.reload(render_gui)
+    importlib.reload(render_core)
     importlib.reload(render)
     importlib.reload(shading_properties)
+    importlib.reload(shading_ray_properties)
     importlib.reload(shading_gui)
     importlib.reload(shading)
+    importlib.reload(texturing_procedural)
     importlib.reload(texturing_properties)
     importlib.reload(texturing_gui)
     importlib.reload(texturing)
-    importlib.reload(object_properties)
-    importlib.reload(object_gui)
-    importlib.reload(object_mesh_topology)
-    importlib.reload(object_curve_topology)
-    importlib.reload(object_primitives)
+    importlib.reload(model_properties)
+    importlib.reload(model_gui)
+    importlib.reload(model_all)
+    importlib.reload(model_poly_topology)
+    importlib.reload(model_meta_topology)
+    importlib.reload(model_curve_topology)
+    importlib.reload(model_primitives)
+    importlib.reload(model_primitives_topology)
+    importlib.reload(particles_properties)
+    importlib.reload(particles)
+    importlib.reload(scripting_properties)
     importlib.reload(scripting_gui)
+    importlib.reload(scripting)
     importlib.reload(update_files)
 
 else:
@@ -189,15 +265,23 @@ else:
     from bpy.props import StringProperty, BoolProperty, EnumProperty
 
     from . import (
-        base_ui,
+        ui_core,
         render_properties,
         scenography_properties,
+        nodes_properties,
+        nodes_gui,
+        nodes,
         shading_properties,
+        shading_ray_properties,
         texturing_properties,
-        object_properties,
+        model_properties,
         scripting_properties,
         render,
-        object_primitives,  # for import and export of POV specific primitives
+        render_core,
+        model_primitives,
+        model_primitives_topology,
+        particles_properties,
+        particles,
     )
 
 # ---------------------------------------------------------------- #
@@ -211,85 +295,96 @@ class POV_OT_update_addon(bpy.types.Operator):
     bl_idname = "pov.update_addon"
     bl_label = "Update POV addon"
 
-    def execute(self, context):  # sourcery no-metrics
+    def execute(self, context):
         import os
-        import tempfile
         import shutil
-        import urllib.request
+        import tempfile
         import urllib.error
+        import urllib.request
         import zipfile
 
-        def recursive_overwrite(src, dest, ignore=None):
-            if os.path.isdir(src):
-                if not os.path.isdir(dest):
-                    os.makedirs(dest)
-                files = os.listdir(src)
-                if ignore is not None:
-                    ignored = ignore(src, files)
-                else:
-                    ignored = set()
-                for f in files:
-                    if f not in ignored:
-                        recursive_overwrite(os.path.join(src, f), os.path.join(dest, f), ignore)
-            else:
-                shutil.copyfile(src, dest)
+    def recursive_overwrite(self, src, dest, ignore=None):
+        """Update the script automatically (along with other addons).
+
+        Arguments:
+            src -- path where to update from
+            dest -- storing temporary download here
+        Keyword Arguments:
+            ignore -- leave some directories alone (default: {None})
+
+        Returns:
+            finished flag for operator which is a set()
+        """
+        if os.path.isdir(src):
+            if not os.path.isdir(dest):
+                os.makedirs(dest)
+            files = os.listdir(src)
+            ignored = ignore(src, files) if ignore is not None else set()
+            unignored_files = (fle for fle in files if fle not in ignored)
+            for f in unignored_files:
+                source = os.path.join(src, f)
+                destination = os.path.join(dest, f)
+                recursive_overwrite(source, destination, ignore)
+        else:
+            shutil.copyfile(src, dest)
 
         print("-" * 20)
         print("Updating POV addon...")
 
         with tempfile.TemporaryDirectory() as temp_dir_path:
-            temp_zip_path = os.path.join(temp_dir_path, 'master.zip')
+            temp_zip_path = os.path.join(temp_dir_path, "master.zip")
 
             # Download zip archive of latest addons master branch commit
             # More work needed so we also get files from the shared addons presets /pov folder
             # switch this URL back to the BF hosted one as soon as gitweb snapshot gets fixed
-            url = 'https://github.com/blender/blender-addons/archive/refs/heads/master.zip'
+            url = "https://github.com/blender/blender-addons/archive/refs/heads/master.zip"
             try:
                 print("Downloading", url)
 
                 with urllib.request.urlopen(url, timeout=60) as url_handle, open(
-                    temp_zip_path, 'wb'
+                    temp_zip_path, "wb"
                 ) as file_handle:
                     file_handle.write(url_handle.read())
             except urllib.error.URLError as err:
-                self.report({'ERROR'}, "Could not download: %s" % err)
+                self.report({"ERROR"}, "Could not download: %s" % err)
 
             # Extract the zip
             print("Extracting ZIP archive")
             with zipfile.ZipFile(temp_zip_path) as zip_archive:
-                for member in zip_archive.namelist():
-                    if 'blender-addons-master/render_povray' in member:
-                        # Remove the first directory and the filename
-                        # e.g. blender-addons-master/render_povray/shading_nodes.py
-                        # becomes render_povray/shading_nodes.py
-                        target_path = os.path.join(
-                            temp_dir_path, os.path.join(*member.split('/')[1:-1])
-                        )
-
-                        filename = os.path.basename(member)
-                        # Skip directories
-                        if len(filename) == 0:
-                            continue
-
-                        # Create the target directory if necessary
-                        if not os.path.exists(target_path):
-                            os.makedirs(target_path)
-
-                        source = zip_archive.open(member)
-                        target = open(os.path.join(target_path, filename), "wb")
-
-                        with source, target:
-                            shutil.copyfileobj(source, target)
-                            print('copying', source, 'to', target)
-
-            extracted_render_povray_path = os.path.join(temp_dir_path, 'render_povray')
+                pov_addon_pkg = (member for member in zip_archive.namelist() if
+                                  "blender-addons-master/render_povray" in member)
+                for member in pov_addon_pkg:
+                    # Remove the first directory and the filename
+                    # e.g. blender-addons-master/render_povray/nodes.py
+                    # becomes render_povray/nodes.py
+                    target_path = os.path.join(
+                        temp_dir_path, os.path.join(*member.split("/")[1:-1])
+                    )
+
+                    filename = os.path.basename(member)
+                    # Skip directories
+                    if not filename:
+                        continue
+
+                    # Create the target directory if necessary
+                    if not os.path.exists(target_path):
+                        os.makedirs(target_path)
+
+                    source = zip_archive.open(member)
+                    target = open(os.path.join(target_path, filename), "wb")
+
+                    with source, target:
+                        shutil.copyfileobj(source, target)
+                        print("copying", source, "to", target)
+
+            extracted_render_povray_path = os.path.join(temp_dir_path, "render_povray")
 
             if not os.path.exists(extracted_render_povray_path):
-                self.report({'ERROR'}, "Could not extract ZIP archive! Aborting.")
-                return {'FINISHED'}
+                self.report({"ERROR"}, "Could not extract ZIP archive! Aborting.")
+                return {"FINISHED"}
 
             # Find the old POV addon files
-            render_povray_dir = os.path.abspath(os.path.dirname(__file__))
+            render_povray_dir = os.path.abspath(os.path.dirname(__file__)) # Unnecessary abspath?
             print("POV addon addon folder:", render_povray_dir)
 
             # TODO: Create backup
@@ -298,17 +393,18 @@ class POV_OT_update_addon(bpy.types.Operator):
             # (only directories and *.py files, user might have other stuff in there!)
             print("Deleting old POV addon files")
             # remove __init__.py
-            os.remove(os.path.join(render_povray_dir, '__init__.py'))
+            os.remove(os.path.join(render_povray_dir, "__init__.py"))
             # remove all folders
-            DIRNAMES = 1
-            for directory in next(os.walk(render_povray_dir))[DIRNAMES]:
+            dir_names = 1
+            for directory in next(os.walk(render_povray_dir))[dir_names]:
                 shutil.rmtree(os.path.join(render_povray_dir, directory))
 
             print("Copying new POV addon files")
             # copy new POV addon files
             # copy __init__.py
             shutil.copy2(
-                os.path.join(extracted_render_povray_path, '__init__.py'), render_povray_dir
+                os.path.join(extracted_render_povray_path, "__init__.py"),
+                render_povray_dir,
             )
             # copy all folders
             recursive_overwrite(extracted_render_povray_path, render_povray_dir)
@@ -316,8 +412,8 @@ class POV_OT_update_addon(bpy.types.Operator):
         bpy.ops.preferences.addon_refresh()
         print("POV addon update finished, restart Blender for the changes to take effect.")
         print("-" * 20)
-        self.report({'WARNING'}, "Restart Blender!")
-        return {'FINISHED'}
+        self.report({"WARNING"}, "Restart Blender!")
+        return {"FINISHED"}
 
 
 # ---------------------------------------------------------------- #
@@ -325,34 +421,35 @@ class POV_OT_update_addon(bpy.types.Operator):
 # ---------------------------------------------------------------- #
 
 
-class PovrayPreferences(bpy.types.AddonPreferences):
-    """Declare preference variables to set up POV binary"""
+class PovPreferences(bpy.types.AddonPreferences):
+    """Declare preference variables to set up POV binary."""
 
     bl_idname = __name__
 
     branch_feature_set_povray: EnumProperty(
-        name='Feature Set',
-        description='Choose between official (POV-Ray) or (UberPOV) '
-                    'development branch features to write in the pov file',
+        name="Feature Set",
+        description="Choose between official (POV-Ray) or (UberPOV) "
+        "development branch features to write in the pov file",
         items=(
-            ('povray', 'Official POV-Ray', '', 'PLUGIN', 0),
-            ('uberpov', 'Unofficial UberPOV', '', 'PLUGIN', 1),
+            ("povray", "Official POV-Ray", "", "PLUGIN", 0),
+            ("uberpov", "Unofficial UberPOV", "", "PLUGIN", 1),
         ),
-        default='povray',
+        default="povray",
     )
 
     filepath_povray: StringProperty(
-        name='Binary Location', description='Path to renderer executable', subtype='FILE_PATH'
+        name="Binary Location", description="Path to renderer executable", subtype="FILE_PATH"
     )
 
     docpath_povray: StringProperty(
-        name='Includes Location', description='Path to Insert Menu files', subtype='FILE_PATH'
+        name="Includes Location", description="Path to Insert Menu files", subtype="FILE_PATH",
+        default="",
     )
 
     use_sounds: BoolProperty(
-        name='Use Sound',
-        description='Signaling end of the render process at various'
-                    'stages can help if you\'re away from monitor',
+        name="Use Sound",
+        description="Signaling end of the render process at various"
+        "stages can help if you're away from monitor",
         default=False,
     )
 
@@ -360,38 +457,40 @@ class PovrayPreferences(bpy.types.AddonPreferences):
     # And implement the three cases, left uncommented for a dummy
     # interface in case some doc screenshots get made for that area
     filepath_complete_sound: StringProperty(
-        name='Finish Render Sound',
-        description='Path to finished render sound file',
-        subtype='FILE_PATH',
+        name="Finish Render Sound",
+        description="Path to finished render sound file",
+        subtype="FILE_PATH",
     )
 
     filepath_parse_error_sound: StringProperty(
-        name='Parse Error Sound',
-        description='Path to parsing time error sound file',
-        subtype='FILE_PATH',
+        name="Parse Error Sound",
+        description="Path to parsing time error sound file",
+        subtype="FILE_PATH",
     )
 
     filepath_cancel_sound: StringProperty(
-        name='Cancel Render Sound',
-        description='Path to cancelled or render time error sound file',
+        name="Cancel Render Sound",
+        description="Path to cancelled or render time error sound file",
         subtype='FILE_PATH',
     )
 
     def draw(self, context):
         layout = self.layout
-        layout.prop(self, "branch_feature_set_povray")
-        layout.prop(self, "filepath_povray")
-        layout.prop(self, "docpath_povray")
-        layout.prop(self, "filepath_complete_sound")
-        layout.prop(self, "filepath_parse_error_sound")
-        layout.prop(self, "filepath_cancel_sound")
-        layout.prop(self, "use_sounds", icon="SOUND")
-        layout.operator("pov.update_addon", icon='FILE_REFRESH')
+        layout.prop(self, 'branch_feature_set_povray')
+        layout.prop(self, 'filepath_povray')
+        layout.prop(self, 'docpath_povray')
+        layout.prop(self, 'filepath_complete_sound')
+        layout.prop(self, 'filepath_parse_error_sound')
+        layout.prop(self, 'filepath_cancel_sound')
+        layout.prop(self, 'use_sounds', icon='SOUND')
+        layout.operator('pov.update_addon', icon='FILE_REFRESH')
+        layout.operator("wm.url_open", text="Community",icon='EVENT_F').url = \
+            "https://www.facebook.com/povable"
 
 
 classes = (
     POV_OT_update_addon,
-    PovrayPreferences,
+    PovPreferences,
 )
 
 
@@ -402,21 +501,33 @@ def register():
     render_properties.register()
     scenography_properties.register()
     shading_properties.register()
+    shading_ray_properties.register()
     texturing_properties.register()
-    object_properties.register()
+    model_properties.register()
+    particles_properties.register()
     scripting_properties.register()
+    nodes_properties.register()
+    nodes_gui.register()
     render.register()
-    base_ui.register()
-    object_primitives.register()
+    render_core.register()
+    ui_core.register()
+    model_primitives_topology.register()
+    model_primitives.register()
 
 
 def unregister():
-    object_primitives.unregister()
-    base_ui.unregister()
+    model_primitives.unregister()
+    model_primitives_topology.unregister()
+    ui_core.unregister()
+    render_core.unregister()
     render.unregister()
+    nodes_gui.unregister()
+    nodes_properties.unregister()
     scripting_properties.unregister()
-    object_properties.unregister()
+    particles_properties.unregister()
+    model_properties.unregister()
     texturing_properties.unregister()
+    shading_ray_properties.unregister()
     shading_properties.unregister()
     scenography_properties.unregister()
     render_properties.unregister()
@@ -425,7 +536,7 @@ def unregister():
         unregister_class(cls)
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     register()
 
 # ------------8<---------[ BREAKPOINT ]--------------8<----------- #  Move this snippet around
diff --git a/render_povray/base_ui.py b/render_povray/base_ui.py
deleted file mode 100755
index 5ecf53b6a..000000000
--- a/render_povray/base_ui.py
+++ /dev/null
@@ -1,305 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-# <pep8 compliant>
-
-"""User interface imports and preferences for the addon."""
-
-# import addon_utils
-# from time import sleep
-import bpy
-import os
-
-from bpy.app.handlers import persistent
-from pathlib import Path
-
-# from bpy.utils import register_class, unregister_class
-# from bpy.types import (
-# Operator,
-# Menu,
-# UIList,
-# Panel,
-# Brush,
-# Material,
-# Light,
-# World,
-# ParticleSettings,
-# FreestyleLineStyle,
-# )
-
-# from bl_operators.presets import AddPresetBase
-
-from . import (
-    render_gui,
-    scenography_gui,
-    object_gui,
-    shading_gui,
-    texturing_gui,
-    shading_nodes,  # for POV specific nodes
-    scripting_gui,
-    update_files,
-)
-
-
-# ------------ POV-Centric WORKSPACE ------------ #
-@persistent
-def pov_centric_moray_like_workspace(dummy):
-    """Set up a POV centric Workspace if addon was activated and saved as default renderer.
-
-    This would bring a ’_RestrictData’ error because UI needs to be fully loaded before
-    workspace changes so registering this function in bpy.app.handlers is needed.
-    By default handlers are freed when loading new files, but here we want the handler
-    to stay running across multiple files as part of this add-on. That is why the
-    bpy.app.handlers.persistent decorator is used (@persistent) above.
-    """
-    # Scripting workspace may have been altered from factory though, so should
-    # we put all within a Try... Except AttributeErrors ? Any better solution ?
-    # Should it simply not run when opening existing file? be a preferences operator to create
-    # Moray like workspace
-
-    available_workspaces = bpy.data.workspaces
-
-    if all(tabs in available_workspaces for tabs in ['POV-Mo', 'POV-Ed']):
-        print("\nPOV-Mo and POV-Ed tabs respectively provide GUI and TEXT\n"
-              "oriented POV workspaces akin to Moray and POVWIN")
-    else:
-        if 'POV-Ed'not in available_workspaces:
-            print(
-                "\nTo use POV centric workspaces you can set POV render option\n"
-                "and save it with File > Defaults > Save Startup File menu"
-            )
-            try:
-                if all(othertabs not in available_workspaces for othertabs in ['Geometry Nodes', 'POV-Ed']):
-                    bpy.ops.workspace.append_activate(
-                        idname='Geometry Nodes',
-                        filepath=os.path.join(bpy.utils.user_resource('CONFIG'), 'startup.blend')
-                    )
-            except BaseException as e:
-                print(e.__doc__)
-                print('An exception occurred: {}'.format(e))
-                try:
-                    # Last resort: try to import from the blender templates
-                    for p in Path(next(bpy.utils.app_template_paths())).rglob("startup.blend"):
-                        bpy.ops.workspace.append_activate(
-                            idname=self.targetWorkspace,
-                            filepath=str(p))
-                except BaseException as e:
-                    print(e.__doc__)
-                    print('An exception occurred: {}'.format(e))
-                    # Giving up as prerequisites can't be found
-                    print(
-                        "\nFactory Geometry Nodes workspace needed for POV text centric"
-                        "\nworkspace to activate when POV is set as default renderer"
-                    )
-            finally:
-                # Create POVWIN like editor (text oriented editing)
-                if 'POV-Ed' not in available_workspaces and 'Geometry Nodes' in available_workspaces:
-                    wsp = available_workspaces.get('Geometry Nodes')
-                    context = bpy.context
-                    if context.scene.render.engine == 'POVRAY_RENDER' and wsp is not None:
-                        bpy.ops.workspace.duplicate({'workspace': wsp})
-                        available_workspaces['Geometry Nodes.001'].name = 'POV-Ed'
-                        # May be already done, but explicitly make this workspace the active one
-                        context.window.workspace = available_workspaces['POV-Ed']
-                        pov_screen = available_workspaces['POV-Ed'].screens[0]
-                        pov_workspace = pov_screen.areas
-                        pov_window = context.window
-                        # override = bpy.context.copy()  # crashes
-                        override = {}
-                        properties_area = pov_workspace[0]
-                        nodes_to_3dview_area = pov_workspace[1]
-                        view3d_to_text_area = pov_workspace[2]
-                        spreadsheet_to_console_area = pov_workspace[3]
-
-                        try:
-                            nodes_to_3dview_area.ui_type = 'VIEW_3D'
-                            override['window'] = pov_window
-                            override['screen'] = bpy.context.screen
-                            override['area'] = nodes_to_3dview_area
-                            override['region'] = nodes_to_3dview_area.regions[-1]
-                            bpy.ops.screen.space_type_set_or_cycle(
-                                override, 'INVOKE_DEFAULT', space_type='VIEW_3D'
-                            )
-                            space = nodes_to_3dview_area.spaces.active
-                            space.region_3d.view_perspective = 'CAMERA'
-
-                            override['window'] = pov_window
-                            override['screen'] = bpy.context.screen
-                            override['area'] = view3d_to_text_area
-                            override['region'] = view3d_to_text_area .regions[-1]
-                            override['scene'] = bpy.context.scene
-                            override['space_data'] = view3d_to_text_area .spaces.active
-                            bpy.ops.screen.space_type_set_or_cycle(
-                                override, 'INVOKE_DEFAULT', space_type='TEXT_EDITOR'
-                            )
-                            view3d_to_text_area.spaces.active.show_region_ui = True
-
-                            spreadsheet_to_console_area.ui_type = 'CONSOLE'
-                            override['window'] = pov_window
-                            override['screen'] = bpy.context.screen
-                            override['area'] = spreadsheet_to_console_area
-                            override['region'] = spreadsheet_to_console_area.regions[-1]
-                            bpy.ops.screen.space_type_set_or_cycle(
-                                override, 'INVOKE_DEFAULT', space_type='CONSOLE'
-                            )
-                            space = properties_area.spaces.active
-                            space.context = 'RENDER'
-                            bpy.ops.workspace.reorder_to_front({'workspace': available_workspaces['POV-Ed']})
-                        except AttributeError:
-                            # In case necessary area types lack in existing blend files
-                            pass
-
-        if 'POV-Mo'not in available_workspaces:
-            try:
-                if all(tab not in available_workspaces for tab in ['Rendering', 'POV-Mo']):
-                    bpy.ops.workspace.append_activate(
-                        idname='Rendering',
-                        filepath=os.path.join(bpy.utils.user_resource('CONFIG'), 'startup.blend')
-                    )
-            except BaseException as e:
-                print(e.__doc__)
-                print('An exception occurred: {}'.format(e))
-                try:
-                    # Last resort: try to import from the blender templates
-                    for p in Path(next(bpy.utils.app_template_paths())).rglob("startup.blend"):
-                        bpy.ops.workspace.append_activate(
-                            idname=self.targetWorkspace,
-                            filepath=str(p))
-                except BaseException as e:
-                    print(e.__doc__)
-                    print('An exception occurred: {}'.format(e))
-                    # Giving up
-                    print(
-                        "\nFactory 'Rendering' workspace needed for POV GUI centric"
-                        "\nworkspace to activate when POV is set as default renderer"
-                    )
-            finally:
-                # Create Moray like workspace (GUI oriented editing)
-                if 'POV-Mo' not in available_workspaces and 'Rendering' in available_workspaces:
-                    wsp1 = available_workspaces.get('Rendering')
-                    context = bpy.context
-                    if context.scene.render.engine == 'POVRAY_RENDER' and wsp1 is not None:
-                        bpy.ops.workspace.duplicate({'workspace': wsp1})
-                        available_workspaces['Rendering.001'].name = 'POV-Mo'
-                        # Already done it would seem, but explicitly make this workspace the active one
-                        context.window.workspace = available_workspaces['POV-Mo']
-                        pov_screen = available_workspaces['POV-Mo'].screens[0]
-                        pov_workspace = pov_screen.areas
-                        pov_window = context.window
-                        # override = bpy.context.copy()  # crashes
-                        override = {}
-                        properties_area = pov_workspace[0]
-                        image_editor_to_view3d_area = pov_workspace[2]
-
-                        try:
-                            image_editor_to_view3d_area.ui_type = 'VIEW_3D'
-                            override['window'] = pov_window
-                            override['screen'] = bpy.context.screen
-                            override['area'] = image_editor_to_view3d_area
-                            override['region'] = image_editor_to_view3d_area.regions[-1]
-                            bpy.ops.screen.space_type_set_or_cycle(
-                                override, 'INVOKE_DEFAULT', space_type='VIEW_3D'
-                            )
-                            space = image_editor_to_view3d_area.spaces.active  # Uncomment For non quad view
-                            space.region_3d.view_perspective = 'CAMERA'  # Uncomment For non quad view
-                            space.show_region_toolbar = True
-                            # bpy.ops.view3d.camera_to_view(override)  # Uncomment For non quad view ?
-                            for num, reg in enumerate(image_editor_to_view3d_area.regions):
-                                if reg.type != 'view3d':
-                                    override['region'] = image_editor_to_view3d_area.regions[num]
-                            bpy.ops.screen.region_quadview(override)  # Comment out for non quad
-                            propspace = properties_area.spaces.active
-                            propspace.context = 'MATERIAL'
-                            bpy.ops.workspace.reorder_to_front({'workspace': available_workspaces['POV-Mo']})
-                        except (AttributeError, TypeError):
-                            # In case necessary types lack in existing blend files
-                            pass
-                        # available_workspaces.update()
-
-    # -----------------------------------UTF-8---------------------------------- #
-    # Check and fix all strings in current .blend file to be valid UTF-8 Unicode
-    # sometimes needed for old, 2.4x / 2.6x area files
-    try:
-        bpy.ops.wm.blend_strings_utf8_validate()
-    except BaseException as e:
-        print(e.__doc__)
-        print('An exception occurred: {}'.format(e))
-        pass
-
-
-def check_material(mat):
-    """Allow use of material properties buttons rather than nodes."""
-    if mat is not None:
-        if mat.use_nodes:
-            return not mat.node_tree
-        return True
-    return False
-
-
-def simple_material(mat):
-    """Test if a material is nodeless."""
-    return (mat is not None) and (not mat.use_nodes)
-
-
-def pov_context_texblock(context):
-    """Recreate texture context type as deprecated in blender 2.8."""
-    idblock = context.brush
-    if idblock and context.scene.texture_context == 'OTHER':
-        return idblock
-
-    # idblock = bpy.context.active_object.active_material
-    idblock = context.view_layer.objects.active.active_material
-    if idblock and context.scene.texture_context == 'MATERIAL':
-        return idblock
-
-    idblock = context.scene.world
-    if idblock and context.scene.texture_context == 'WORLD':
-        return idblock
-
-    idblock = context.light
-    if idblock and context.scene.texture_context == 'LIGHT':
-        return idblock
-
-    if context.particle_system and context.scene.texture_context == 'PARTICLES':
-        idblock = context.particle_system.settings
-        return idblock
-
-    idblock = context.line_style
-    if idblock and context.scene.texture_context == 'LINESTYLE':
-        return idblock
-
-
-# class TextureTypePanel(TextureButtonsPanel):
-
-# @classmethod
-# def poll(cls, context):
-# tex = context.texture
-# engine = context.scene.render.engine
-# return tex and ((tex.type == cls.tex_type and not tex.use_nodes) and (engine in cls.COMPAT_ENGINES))
-
-
-def register():
-    update_files.register()
-    render_gui.register()
-    scenography_gui.register()
-    object_gui.register()
-    shading_gui.register()
-    texturing_gui.register()
-    shading_nodes.register()
-    scripting_gui.register()
-
-    if pov_centric_moray_like_workspace not in bpy.app.handlers.load_post:
-        bpy.app.handlers.load_post.append(pov_centric_moray_like_workspace)
-
-
-def unregister():
-    if pov_centric_moray_like_workspace in bpy.app.handlers.load_post:
-        bpy.app.handlers.load_post.remove(pov_centric_moray_like_workspace)
-
-    scripting_gui.unregister()
-    shading_nodes.unregister()
-    texturing_gui.unregister()
-    shading_gui.unregister()
-    object_gui.unregister()
-    scenography_gui.unregister()
-    render_gui.unregister()
-    update_files.unregister()
diff --git a/render_povray/icons/pov.add.box.dat b/render_povray/icons/pov.add.box.dat
index c6d27a5ffafaed7b795ff1db751dd62c13fb482b..8df6b7a72c3288126c133aaae9c211150a710231 100644
GIT binary patch
delta 47
zcmeyR_)Bqui^>M$IYo06qB>`mt?hJ@o>AyjxWRZs+nl1PPPej|Wk3<2*v5=W0ssL3
B6vhAm

delta 47
zcmeyR_)Bqui^^g1wY6&%lc%j}I55paW<|9}^<neFlh@WJPxEV7)c_O$ifzo8Bme*<
CCKgoy

diff --git a/render_povray/icons/pov.add.cylinder.dat b/render_povray/icons/pov.add.cylinder.dat
index 9820412a3882c2e28daec7d7c3afb9612d8c271c..ae21a9ea6e42cf188a9d5f22a41087449d661feb 100644
GIT binary patch
delta 566
zcmW-e&5ENy5XWIJd)edMq9%kOfko(t*kg;xAcHW1M5ArQd=NueLy+DOI&lyrD>w%i
zWMzzfp8Gz`KEpo1b`M3>uW7oftN!&@`KJ`$(*AHi_InC`hrfc@iCiSuk~_CO;$kNk
z9z!jHpqQ6Z>2+y~+f9p`Q?qJL^|@Kq=Ze%3XB}1KcDXHXm;9Os`ITgOKr);W#7Pj&
zl9$9!Ug9kF<7~;|GDFuTic@x2B6OWyW{cUyB35V-^K6CY**KXIrx_V19qSt%YpJ%?
zQuU~9)gwHpJ5F^6_skF4mcP(9#>zMuE1qM8yy9$u0(3<I|0DK!3yF_?oME3gl?N&B
z`L?LSic}$=qFM17+fBFa(r$a-b?JTA@9#&>52e2M2MXxAUsKSp-y*6|(8-;ApHih6
zw-XB5NhecK4eJpF^{5$BP<o}rqErnjc=bz<<%73~EB>1L6hy8&cSTo30+)X&#18X8
z?8Jj)kT@T7*iuAyj-AB8>@FE#W<<aac8HKOD@5WJVlgMKE!iFfw)gBq@a#VY5SSxt
z2!S=UB?$D1F^0exn<EI+sV2)%(PVuB0aV~ub;_y9Pr+jd0o(kV@4gUA>D~VH<KgF@
H&tLunO4IAc

delta 556
zcmW-e&5GMF5XYg<&<E%#F(w-VCM@KjQ?D{EW-+V}>w^h7__PKGSx$tK4TunMLlET4
zN#uhmrkFL>6ZIMT0F{cE`3*ud`p?YYr$0|WzZ1Fd4ss{KZ}Mw_lJ|LJgysx|X+CEO
zH(tn(6$B?#m_^$1=GbhSV|}jI^|`v%>*^|Q71gY$xLxj-<>k&FJmZgC@GKXs%rnhA
zV<jyqrX?y7MrAAzjz#o{GelGnXVGJNpDw5O;1-bJ=3fKiU%j(WHGS%x-J`p4j}CX3
z!|j8^>;v4}TTOclcgpq-(=XhOxAxB7TIU2rSDY>*RXxh|9|&s+6f?|9hP5c35v}>M
z6hzzv(OuMQ-D9(9S<742ZCl=L<*qwus!0L)@wt-Ea?*|EM1t08EgjXvW+Xu~YAp$b
zSq&tp2K7*aA}vhiO`&{=i&W)<&y}8ai6wZS&j^|sGb1we;Nw0@l=V=8dbCHA|4h_6
zj2U7HO_;i)J*H0HX9-L4J~y~=GV=M!IH7zVnqdk-n7&~M-tg-J0)G&g5CmqZygTxS
t5O_m>0D(PrEI4s2ck~ScIDyahSktjy7cT??wet_X^^;MU-So@q>pyeX07w7;

diff --git a/render_povray/icons/pov.add.infinite_plane.dat b/render_povray/icons/pov.add.infinite_plane.dat
index 932e0128530843e99ed890f48a4a469b20d52afd..4de537bcb9468125bc9b9ccfda845befb5a682f4 100644
GIT binary patch
delta 1753
zcmcJHNmt@n9K|<Xo2uS)pVKQ>bxyC!i9tdLqks@X5`r0+WDuva5J8U3(Syf0K|lsU
zv``sR<;1Y*MeX}z^rp7;J*xlt5WRT!-uJury$9<*c+>gY_bDO&=hTn<xBQRy->1fT
zp(T36sr=M?GT(}HR<2iCkvs54q!;c+>fv6v5vhjy;aa#F8h}?qUxI^BIrt@52}yyk
z!E#UvJc3JsC*d(r5}pJpkPM`R?Lb0E3CTc_e-cVUk$(n{2^l^uMEMLK6E3)CzQ|v2
zFW@U8pXD-qgv)X(pgA_nopU$L3!GQ>g$uJeHo~1Px0!RU&D?<A0>5F-mamyM6I#x(
zVfHhSTOcjwn(09P41C|8V>`ZAw!?IML;ov#w%qi!m{Wh#ceZ>7q~i<v_x+*ed!IP;
z-}z4cSH7n2o*w$|AvfqNsPoLe|DGN(L;nfWpilh|kniaLlV^hdF60OLgsD>vTJYtW
z0Mn&LOqUjYb?QXP(2KqrRi^~sCY5J4sRKIC^niCMf!+YRL2ZJ<MkH%gj~vlGDAuVB
z>X@pLqHhZxz<^$())gh!s4en<-h$;AJ`w07xki3e2-)|Jf$~(66zF5B>a77kgodY9
z$t3Vq?<#pjR=vm6C+{Ko3GyXT_44GZm#26#;T2cCDW&kjNAOjj7(+^V3&bJ0LllS<
zOyfktdqh?|mjvgH6FgZUDxM>9;DIHP_7sR60y5m7^~B*#8LxOaBJSb51AOcmK#qCh
zp0uZo7YN9B%(IJ^@wA7<V>l-gySU^o;{$vGW7$1{1cf_>v+kIi#WQZnodGSmB)9CE
z;Ig9NSy#!GakK8IE9z!lQ5UP|Qq+}o6`duQyfksiuDzwAGwWiV(Ir+Pj)^PhEIRj=
zWaq>oF9C-}%sDR{MJMZsI$3AVapA}*8E&!zMvB@ej+~=tzi_bjsDpLv*^Bm^L$*)s
zvO<7CUYyujThz|l<;BUOtmMV0ea~L9742DD$+o9(D>Sx=RbFJR(M8q<3ahMWldL6M
z#wuB}HmEHyuud#8)G=Gs##&;KK;f3LmMxNXV1X8NY@S$lEoDnaX`$y#F$=2@bJ;R5
zkIe%`O=EM~Trllg3Z}Ao*TR|N=9q;u#?725Z9c>brXx%&!{7+3zyKa#W7Cf55KEhR
z3`WMjX^agZC*a``c4@4bfEdPD3N|~YW5cEK2<scihQ5h6BuqT;1N0E%4Xeh4Y1?>!
zreITrcYWgsg%vhJ`$pc7G_D#2GzsaW@jyhkjiSDa9UFQ|jfwgiR>dIeN2q|VVM*f_
zc7T2a%_9du*0D8g6Y>@&=+`j;?HNY;9^?&d9oqoD2|15!DE}C07_`Vpe}dEuqP`2c
zX9ys9L;zApYlg02s1rv>7ai)lQ0(jS$f>T5o`5#cIvUjN>jKDw(xMOgq3!`a)m`Zu
zkY5*uy3oSDE~q=xUg=LE=d}B}d*BcHJJ5UmnYO9FQnL1SA*{`5LkoAvtM*QRu4zJd
zu4y4neMcc864qulIc*1d(Y$IugLaT}%{9_OBATow4AVApjobqNjNE|ULVJU>L0`Zx
z)GwNI&5Am!iGXInFVxS#qUy9Nqb{nSRL|;|Dy@pDOR6VTQJt7isp2YW{z+9*CFZx`
zW|!t4;VR8N&P(&s+*k1OoILwwt}-jnmglOogPFl>ZDuf2oo!6_X6iGI=`MKd!`*aq
ty7i$0{tbWg@0<4E?XT_Qcg=S0U9a7Eck(|T=G*Uol;8gH*U!Iy{s&qkqdWir

literal 8090
zcmeH}NmE*T8piWIT%_^=vPsoV96%5h9FR#tb9;lvs7dZMX*EZqR#e0(_O0rwBr2E)
zo7-{12^l?%A|S{N0!N&{e1u-x&*}Z$rMgmGEciXY_j&&%a`-Qhb4B;=?(W@VJ067}
zIoRPx#sB6S+rf6o!<fTm4)pW}6T67L<Ix%$dpY*lVt>EvPvTeNe%TeW>_J%>ZMS8&
z4}K-2Xr<`xW7;CF%C?9<!CPXBxP4HD9y{ej;-PXyIZvz*x5@-@OAyK~f*@Q<J>eo)
zN+}^#mMP1KhXl^x6wcrj&fpZz;1o;A!uu>dU2OmSbfJB*ec|bR`*Pb-`&`>{+kE?M
zn_8}Jo0Y5Ovu!i-6`4jpBU_Qp$Y06(WpCs!W&N^O@@d(Z))iT=Y_N4e*3&xJ+ACAF
zercVSsan3as#?2ShgyhMRm(py;RQZ#8EWZn?UD|)Ja6fe4kJIdtV+MOD5Zb2Oo9JE
zev+<AKf)Iz3(_v>h-6s$S*n$+N+%@?5~XxeqLqA>K9h_{x}=@r5y>;jl2|KQluU>}
zNG55=G$EeH)G6-6zE3<V8j*C0eiwI&C&Y82dGWGHD_#<hi{?ZVv<tQ3PSGpTEAboA
zsOU8~CL9$l3$?;!(YSC<G%o5FjtTqWvzR)BFGL+Qk;_6|vsO4Dd@mRijtgg+XN4Wj
zy}}O6GtJ||!RGgZ0in8C*Q};{Y~TVN+G*?+f}ZA%X0<>kP&dC7ycfKs8+4i`Gy+9a
zk3b<H1gfTK0h&e~-Jm0yR853H(b(OjXc}sSgHuQ}=roPGMh(pdU8AC*yHP<CIoznM
zpK8=J=o&PQ!wt&%VY;vHZcx;B*DD&(^K}jH>d`l7>UDe#e3GxMSMaf|AK_2(-_edq
z!^f5;PsbnOD|wTA1+Sa0;J@Q3c_VxcPsh{H4LW$8j;E;W<|%lZI$fQHc1-VhQ*}z-
zaP3sxJG!sMrdC(0sZ-Q;*C}Wshig^X6I@m8a4oi6?72FwrdGl2u2q0NNP;_!J$jCg
z`<64!CAbPs4_84Gi3XjTqvL3}gEj9tZ#f+`Jsbr`U8Ccu;k}p!YsT2`IpgeU4$YaG
zX?lzO0Q$k24t8%%M~#}Tt5MUAX`DTaQ);#rT|aw_Jy0{wnq`l(J6JE+9qfMgC~J)U
zch)GYpFPf+!)BSSWofZ}$$r6JW@%Z=v}2m6o@0%(KqUHVE$cOFggMIU17EW`tAAs4
zvX-i~)yweTS--LRsz;c8@IFkPv{z3u=c^~GpD{<6o$wEsma12o+UiB-Dsu__Fk^(N
zWPD&wGP@YVjAu;rj8!xX=sq)68H?zLs)iY#7+<Sa8PBVRs=62-tG-r!VyLSAU`)|O
zsw%%$sj5_!UywbOL{&F9h@7tYQmLx!sT`;ntemb`!PHwZP|;I4Q@&C$Q}MFmO?iLC
zOt}WrtMWJHFDquBXv$~HXP>B%^N*LG%srWZyo6l%W%2RC<Aq;7Bi#jG@u=u7oFK_U
z<mR+syNTS81@m?6#&To6P9Tj}iEHz<@hXKpxJqB9uJ$j}mj__xB7M1ck-69hbNS50
zZa$aayVyRr=Xdk&JawMS`_HNK<NWry<=mFnQ>OFn^B_f?2f%!eI@eQ1bXz$CrKiHV
zd@e+7<@Pa!a~IisE|R^-hI5<Qz1$YKi;QLQmf3jbG84;gWOlNf;5Kq2qd(iuB+o80
zaS;DvW<9Mx+W@zasq|GkdA63?Os}U?>Fd;0I-R;srP6Du4RmYCjnrCdExC?7OJ1ka
z;7#J1ZW8OsZ}IiSS~3&ANu0sgFlFL}*i9lEE5tJKZ?U!bw>Ugnh~=V%Xf~FM6r#Ck
zE>a8^qExsT&PBjbF-(PuA&O=Y87u~=5ETG}Af5hVfC_*<q_60s{Gb<kT=Y^t>Iggr
zJxEW{Lmg3`g1hL+xeIQ}lXDf^IXCDmxU<fJGv~@WZ|nu9!Dw=roLR?>JqtHGOpc5l
z?tt6P@N4qMZZKL%v;B;`CNuW+!*z0vJR`4c*QCK{J+zQ6z1Oh6pSE4u&d7D!hHd@O
zV6@q+hbh~o^~$zkMFVak4aP&84J56HwxspanzC(LwyYc0r1ipbX@z4l80{9)ny_40
zl5mH`ZZR007KbHnzOW=Lo0e^}9*xWFM3XmNShmdD=1nwiv&(ESddzOK&+Ik$%~4a{
z6i0Jp_Lz^+_~2gik=YABrrqE*>rFf6EpQhZHBrXADPp9IQPZ|@&!h+Ukvj+b#%*Kh
zfHH=`9CGKtfX&W<*I+R28oY*`gA+sUAP8pma|eF##NY${*k$&8cs5?co*}*GHMsQu
znsw>-_g(tbZhFt9kL@IOUHZs&Y{#V!=}))40dMjs=?xqwy`V3N4EU2i@F?j4JxMnh
zNcur{(gmI*12hvSpeyN2cmu)23Fu5Xz)&JcGadpR347cdIE{xudz=Kru~U$YlQA%M
z7)28e!z0l!{4i>Zz#|d3En*A9!x1#ma0G4(TTkJqQMmQg8iI$SXkwu#+#0e5;lUW~
zLCXpJB!<RvVhO-8!2=dQ+#f?@@mqXwUkr`KXFY}=$Iw`htzNh{h9>Hb!L45F5&S4h
z`;pB9_e9Y|JW;sKV{^mZ5!&5{F1Ra#ChUs94_%}a?hM0EonbiXv^(&89H(%*!vP2E
zc!u^6?RF>p#2y5lb{FWfyGT5L@`QHM4fm4)&`o+k4|xRoNI!T?`asN}mpmrZdl}q<
zw|6^tKhx{`_wRQ<;`LXb>EAE^cK0*Ae#B=j{f!@UXG+hp^c+ifpmYaHcc63!N_XIY
oa|izFcUr0cxqg?IzK_!PQMv=AJ5ag<r8`i%1Eo9g|9A)f14Mx1s{jB1

diff --git a/render_povray/icons/pov.add.loft.dat b/render_povray/icons/pov.add.loft.dat
index e340425ac8a45427e5113e1c25d76a930fd1619b..cf73b4951c2533fb0850d90c6b80d4814dd1ac43 100644
GIT binary patch
delta 4199
zcmXY!%WooEe#h@zDVr#YlvT3GVpdUBS&S4#n$<|%Z78aWdILrCG*n}Q@hzwaRZztN
zE)Dq6Pt$H2Xt&+%$JoYP+RyG*beGv>8_i!atL!IVM)7g|>imA^6s7b1eyTXU-TKX+
z+rJ@^N8As8`O6PKJQVI%0%%Kxhr*M>6YZ<^M0=7)%rE60(7xu8{WbrpeZ>q(lN+M8
zr@hy9MN_l1s>y4D#x(>@L`2hg%-}Fb##z$v=VZ||C+BKz-qoDEiylF`>}DMth*@n}
zLto1wW}TdtN7K;nDSc%l(pLtE-N;6yr#)ACN;lH`|9nFmXwS8-hRWiM83V14*3&j3
z?}a^KSKJf!g|WCVj1bYZ5!${s772=2#U9GLXk+oOE5e#;mTlEc+qUeQY1yV-H4Uq3
zRnf1SHM6E+46C+ru9{WN!1@hsp~E7da#QV6n{t=h1=_iG#{E${L!5FKi02wI?hn8I
z?!SNc>mPQMAOD@?1Wn3H0w?nv*R#6DhOz7HX=BI2Csw0*$e~FPM$1YNM$#ZAWI-P_
zT$d5EVpdodveL4UL(B<zd>#%&O?)P5lFXx{$-In$#L2jiz=?<=hm02ptl~Hvh_G-m
zfNN#`IrQrqeQ3LZxMB1bdKz7$yU^G`71@S0ur`c=)ko}GJ;a{%9817~xVyl^dGGGJ
zSdfdev+wS?V`q$P*BQC{=#Mlq##-OQg~h62in9-M#!hXaZq!z4N-b(BI7dyzQmRoy
zF%hc~BZ9tGg+dm_s?miXn6vOAiYpNRxX`|F4iC;6@I#w6&KBA?E*9=I7UyXrpg$s|
zrQ(fy<A3p%ijVS3`7hqQHEW`mTFTFwSDnk&qf|g$N?o?jJ7?|3v7j7K&)UbG&+R2C
z#0TZi?ZeJNE5wHwhCOJF+xvuJ*$_wXlXqm;<Y*x%g@(=7%~!q@QUVd`mA~n4dJ#$u
z#;HxO>yd6FPNjnBP`5!m(nyDLRGucD+aQfRy+W@tu4f=yWy;JtYxwKF;hhIAsWS>0
zYlO?q)#kc?6`awRlo58rF1JGRWVJb`zOXae<w%%txpumhCpXLu|Ai&tE=RajC*Df8
zZukf3j>z5cCW>k&(u%k4<ZrQib(4L?ZibbP)QYt3)$gf?)N5wQykdnm*OXchsqguR
z{JYRTJ!IGx<8e*o#fSWuIzTPthg`<{Ub@#Giig3^sD<1mJ7|5Y+*$Yfao~*lOa-mS
zm4N<W-?(=c3Fwb2k4os)Z+uu#dISq@{CVq9{!wZvHEYgWSDi<x$FYEViQakVacoHn
z%4hBK&T$7lF2sL^9fuv5!G)NE)?sJdX4nw^_sO{Zj&L*^5`x3#JMy|Ip{E31`LCOs
zeuPp2YH*Wyn|{}e!;e(3+khWlI+P0LC|Cg5^K_0{p<w}JUu9P4GGq8<2!0;83SPnj
z<ZQXQ0>Q6>b81E#VS{zUD<s#>lR0(6d|_SA4U=%Lo$5ev=8n7JVFC0`byS#u?2dbo
zU;zYII|@v=lOLpSu}$_h1XnsjTY?4OV&7rGyU>sk+H8w!!UCA^E;Oe0X|@&eA$b0K
z>7h8L4ucTf57tBO_tLk@y?z)x4t%CUZm_;&f2%y$ch+&>93}*<CHwIrC-ob5YM&a@
z>TPAN-<B1FH~GrkoL1-hcC}mWR^kSpKjVJ=wtT<Zt$0<hk}(Tb#-@sawLrl_#WD)U
zI%-Ge$Q~Ix#<sCtT}O4@j2js{ZU+iaR-WWAV(u8sI`tD(Jgps>r^eUf{VMsI=POK=
zS-&7Bof9%ASN^;qlUQ4BU$rlgy>NR@uNEW8R;+#P%^TNluSOhV=e(kqYqAa6qa6no
z$0^qJdeJ*1V>0rGjTiM7u8yi+Pd0PSWQ%qvH)vr5W4zB2wsArZ+lPeQ*00?=TPA#)
z_fMl!Wvb4f-m14LKBC0<II`&c>9*R9ZATR)9#vwusr%&pa#!_Y-B>1CPzupNn#u<9
z7Al@vh^|N1V@J`EawPBInw`jc%!*lRJepB5(I>e;{>jP+)saFam?V`YN6}OHG<v`M
zHT$)|sC=ABp3oQ61#`vDnK>sjF)GF&3th1<#Gd$qk1<I)$<Dd!@U_^Jh}08v;UZV$
zX^!P-(UFNn<RY&Nx_HQp>BG>Fd%+LGy69jm$>x|`m=b6)sEpWQcucc|;aQeH!Ne2l
zTD%qRWIiOL1}18!2ADACx&i`t6HM6C>UMS8=vL!K+*WKbQMoO5E8VJB1rtWWqKec{
zfi*AzD!@c_-8?dn?2)|#@3)a#70Un<W(G_I)}VwzFlaLCOqKcxP}oN%pjf|O<tx0&
zR4>Ry=cEHB+E+f902N@Ob3tCXFX}x9Ou$z#LFSEKt><7n;T1su+h7UpfeD)|m;eEX
zaC%IJjiLVnxl?l-FadgK2d`Sn9r+mad7p2y<OCQVw!wsV?aD-M^X*d@G*#wL=h0gg
zOejihsv?hq2~Yth<aqQpb*tVd!2}q{!0CblCK6QEQmvR5D}a*qD3|~hU_#vnA?r~q
z7LUedl2HIfI*<nxv7HE@P?_XW^hiFH@0ahBU$bB$!pBeO6Y7GxVy{ATMrLFx211aB
zzyt`0fs!OW=jP0H7)*eHTsX%RIWWPnydygzk&1kg(}hE3#Exk&0g`l47aSrc*(9A~
z00l>jfPx!_S%wesESO*pL)YSU_*MWDl+0`heX%F>`R78H@6`s*b7#Q!gy+IQ-AWFU
zeS4z@8@Mjt;ReaA<-u~lvT63spX<*X?Z#kvYh|!9C~sBzl`UzY^rb;$OC6|Wu(DMi
zl>6qU-M0tIR%8(Ay1n|(^-Zxa^~L8--|f14%x-wd4(Ye-Yj(G>=fCrI=^^`;{TQ1h
zC$YWy$lGnap<lBv*-7#+Kgo~H56-Ce#(&#<=}+>9rAcw3A6R2^5*`VA+<`QSjpa%4
zqdw6`q&8{n)nEFrnp^${ek_lK9e>z-<Bym<Za4gjdC6|G@4WZT9lz?Dj^&yS+pX0t
z$q;SPs9Ub>8Wp{4lr6a`S(4SL)!l|s*6}cyY9(Su9KOcYd843bjjWl_W2UOt49lz;
zhF&o$MwaLaGhs$5vL)Afhx0hY(oMst2^R106$jf*#}-YYCghr$SMUpD)i3!Pmt&rV
zHB#_b{X{jrmZ{|UEUR%vQu51wdhO}6^s|hT5|+gxi=6|TewIk5(-}3X;3ecOySYZv
z&Qvm1I-MYDMqL&&T#hfZYmCmO+>EvCuCN8B$Y%XqL-W>}WncGkJ7$I55MYbg=LcY*
zSK9ys1Aas3EeHSueS34k0N3ZcJO}^-@EQ#KT<?Ja5U^mNyafifq%C=%fPe@A1S?x$
z00e*m5Fl@XfIb-56t|?l1jxX^9y3}nK<|Qq-Nwk<^LObzW(Wcflau6LeS+E>dI$zU
z02mmX2i6DY4Xk|I1OW>M^p9X*68^xCgaZlW#3seV(nq3?Y9E|Q<0W8u)%?I8NMm`&
zf8QK7N6aL=$Gu|S&@Vy29%@w=hB#)!sN1e3Rl$H!ue#QP05D+5l?4M06AXX=FaWP(
z6%b&70T2KNKmZufYr1V%Ai&fs#K`JdFc7JLfjTBS3j%E2tO+I<s5m-)Z~!$hkZWdr
zFhDdgpmABe%Qcc~7C=B^ExnfGGpH3w0Su(q5?~;sB*8!tR<1JXXHUUEMolaj@KX5M
zm9!JpOeIayiF5)4q=XDt1}tk#3PTxdh1Hk>ll3!RuCdlEk}?Ru4Vg3QlsOK43Lb?H
zsMF?I>zEu<$DyOp7j>4JspsAWIc<In9#9kNI`xY-OHHj={n9;Xee6tHzi8K`S!q_8
z+f!@GUJB>oxlCs2RGyW-RA!Y+cj}*elh&lY-<t7L`BFG)eZuDYf<6zQGLzth+NX}m
zN&BcJx0Pm;#Ib=&wiUb$qoHUUc`H<5ORO%21zKSeon$-Rrb<knEpal-^L&y@1Tz$q
zr&4T+Rq4f#A)hC%=+PFda~7Kl#aNZ+IaUm_b<X15#jY?(2}JaiX1bGZFSl_c?Ay<%
z3;LA#6g;MmLZ{6O0u@j@h6dNES?b)I`WNJ5@Do(HPF-uiXj5x$&*}$Q%*PHiSg26>
zvQUAY@#IpN%MeeUm9C+}r8}!n{mDYE_Kcs)Q~9WM(*D%Bpr`D4coO^w6^?Pk6YP%!
zqBP^3Xgl6g7=eZgs6|PrLY3H(80J`kPITf>fhtjXCeO*V#6kl#m<U3Hg$imYM*oMz
z`D~qcX$#`v@u#2y&$Cd0t<x4~#i?je4GM(wMK9e+K!qs23!=fBKmEh6e{=I+|LFa=
m6aKAp^ZS4P-yd(nzx~(0-%0(;KYh0U?eA{>=lB2lxBmw?ra))_

delta 4629
zcmW-k?Nbxkp2uf)>sD>u+86s~Z%x&1ZSB6g<DwaJfhYzss8yJx)7|NZJSPH%nUQp!
zK|{!c>2!CxJAtyCJC@h0C=!@a3<JtlM2HjynOFjrGk?dv*gs(JelvCIe9!s)&N(#w
z`+gD*|5N(>R`v6u>jL%J-~ayEXNR#5aRb_k*kSBM?0EDLy-PR}x`gB7N9ZT69$r0h
z)$r{h`UmLY)uVB&{I+%^97T^ukA6FIP8gI1gonZdVb1kR*brW_7Wte!D4mlBpaXKh
z+$Rl67GY56m(6ltQ8LSCkU|sApqYg}$t?6k`-K5$=g5G7?b12K1K8;kd?mKzEBQpP
z*cn|IJ)*DV5#3O?=z_XL9&5bFLD8YCzzN6*E+}#WFL(tX=0&&Q`7PxVIx{cknpgMM
z{JOUat@>)dn!hGg1!M#bS`~Dt>ealmzv5M)Xg<ZKVYlqB`87fIt3u^Bl{*n;3StH0
z1O6dD=X%J`L+ANN&__jnfnRhj@O3CUbWx}am<tQg1>q6&5wde=n03mE!afq_q4Oy7
zoGVpG7Sj1-ApuPk;?Q^@RuJ;BLKGTDPv=uYTKG-#Ng*L5F;Ae!1wmk<f{+*TQ6VP8
zk-{;ILjD!A$-HDYnJs1u4sJ4=!WOg5G}*0A7n;Jh&}5tJwy-5&kFeSCxwB7r1^w>Y
z=lw+&?{l%P{*IrlR*-YG+*}FoQff+@@=M7LAAByp<B7|I!uwqC#pQD$#^{9+K9{fK
z5b``SEYI<*i{p7H&%2;5-i;@?c@NaXd!d8uAULrI0FWIlvK?H`F&1W!?Pms{1I#(7
zndxWx*goV2*)u{PemOIs>thFi+#vGU4X;!|&Z%%)&S@RDb9%@BT*t*urk0a)a>vgc
zvWRN=YQC1M7W4vk7Br|<D5`j8wNQa7c{wlV9(24ap~1n2;sbH6G%r4cJ`(4lix}rh
zb#XylEG>wSpo?N13KCGWhaI<Zb{;f5>Tpq7=rB@B`O?0$Kh^Qmk0xN|O?lJ)gg05l
z5cejaQE$vE_+#EURPaTia9j|un)VC+4h}fb@!lT?1K25G9X}2f_+P=<mlC{(!f~M~
zZFC%#UUvMJwmK9@O?g{xO55@lbW7fZZpyFZ;!Ek3yaA^>+nUm`Ojq(m=Dpaiypz97
zbw#?8@8mswSN&t8E8G>?Rd;IJdRO>Z@I+`^-<;W~o(Ofh4W5na`pmPc!DC>I+_P$<
zwyYaDBh^bU>r2(T)=Qg+KC-UOY4_DW(rg<b?yI-e@09_<V!KczzE@_I8TkS+WU~@8
zvLefpl_2f3Lzdu&L_2uGP3GlEDKELnfFndr%27liDol;hQDsbt%A3}0V#AJzjyl6?
zNF7slY`f$(5p_;FCz*ie)<WtY@s8dlCz(8xXZb3vyR|m?o_j|Z^NfrXS+gQ}z<lt$
z=M?9RQ)Y&07pm6kNB3dqgJ+iho}O`9Y6JR(>ZjmQ_%L*vx=(#in`?dQfPNJIHS#es
z=cqgGQzpGv?o&TTen}lAmxyJe?l4x2Vy}FZ{FFJ&G;Gi8%Y>n9$Q#AO%*Voq{Cd&4
zVSi@tDxAm|@*naC<@e&T3<{roFCLWJl`m5$eB_<nuI%Z5jC^^eE4-`j={vQq@RwJP
z1-CKVoH-FX7VL6wR5xeVXAB<L!90T<jhc~z9n`Yks4Z1{X(MGK>e^CuPU|C0gxPjq
zozrfs14Pkmv)H~@ZmYA(1^6*!n~`S~MYh5Y(k@F1J`%KpwDSZkK-t|SPXrvW0A&wR
z0Y{jMDq$3S)4F5ZCdO#QbXX0b;5)WGVwa3Mqx7URpz&3=wnwzdcl0DP>C7{Hm8{Wv
zn>;8o@3}mqpx|U}2qqjbAKf23igOkPAF5ek!bkU~;9+Q%zKw!gYG#=5Dfr8kqwsBN
z4h1*YOnRSs6#nJPudrYV1vlx&O0WDe@+(YOA{w@3!dNksjp9-Am((X%ux@>3H<Y^y
zhWugXQ>OT_ux{Oi30;L_nG=~0`HzKzax?#8x|wZHwKDB%=d<RVd8&oDmAN@3XXPpD
z^!fbx?7{W+wS%-gmCa_Sm}!5G$r~pOlZIphnMBq<J%-%MbUjy}zMH){eRFCI*|BNs
zG?TaH4awuF<1wt{?iRDAYsTxwi5K~m=@-*S69?&|xOvKi?54V|ud6Nfjna}Wl_A}t
z?`WHdZk5%tDh{cnHl*)kZ68rVER@{Ju<WXwl6#cyavGU*d04tC4a<$nqPnQulW$44
z%2&(j@>PkfxGJR9T{f0a-Y8-Ti)Q7NdP-hb8(O2%miEi<OO}dRGb=9wO@A}ky4()6
zN6mrrLGxuqf!5`lK{<FcaNci4u047%axf-`vdA$3zuzA;j2gp+uM@~*L;m1cU@W*2
zsQXuZcX8&fe=L{?CPLN#<6{EHi(`hbk6*1LUH2P@O(VweO5la>Mc`oUD17v-DP+EE
z8rh`R9j_g4oGrS=Sm+^gh~A`k$T!YgoXXu|hiH-_v9j;n=TyGHtDM_8%nY+V%qg~;
zPrC|y!PTG_sRntExy9adUggrrlC+C<JB?hin?LDZbl!6=Qm1IM^Ax*|jq8qmuFbu7
znMn(BpaDF9hUr%3eD-|aoC6K{X7=XP&1pGnownxB=Rw0%`<e_svRTjo?~N12>xN0t
z03JXCykD7K$=7ptvv+eh5htb+ILj0Pgdu5|Jf5lpmb*FAHB;7j9W;Ok&~W`AZJsjc
zOxaEKwZ5*m)E(`O0vgmI(C}K{RBx4UNopB1z){ekwq#XQ%LNfM!2473DYZud4W)tz
z8sL4S($E&wd-6Ty7UHy6Oqa=uTX9vo%RP#*yr^JNF)L>6l)4T88yaX(_RE%vMK^0N
zU`^BCy4(u1L!iNbKG+N*4uFP`90UzMYoI;a4jqhu2Ds0_W4|9X3>(9VP$HNO`h$M|
zSO7GD2hb3_Sp*zofke<6u=*I^@iD`w;cL(kyxXB6WExostoUB|4#o~fj>4cJV7?3*
zsCCe=L$+uOZ6SxqP5QNChXf7qeh7S!REufR`%cgR_uWnxJ<Nax`V^aXr9sFrJIq|=
zzywS$0t!Gx0wNb}<a(HH9yBoboS=a;J3#~8Aosa_XPW~J;GxE8E{)e&l~XwFMzy4t
zs+@)g>!I1v>L@N1H?$int8fxq9i9DF|5l%x$>}+zT#+P6|8_R6$MwmXDP-NcS9j}0
z|7=JP>G7FKeNxZWa4V7Ur7Oz1ch;x-E236b$~7K;u3R!Bu_B|Ebg?2XQFZ4${g9la
z|4P^8A1aTO1#+IAqgMk@N0)<3<;O~0zEA#@zC*8#J{^BL`Xt{d{dnVE<-U5S@^pMH
z^)$Jh`6>S-zw9jj$S<)!xtD`aJWrFWndQunH<qQP@}0_E_0LL!d*b<#f1u2(_bQL6
zCARMT3v~y73m++U^?@RY@lsrjNeMAoj=Q6L!WAvY#e^8kj88--;<zrx-3cjKPD{~=
z@mMq#3r2h~Uy4hz6Kpg#{!Q$g*hr)pMEXXo5G%%V<5RIr?3>6)Y$WCrJaNwin_{jp
zvBG#RmP_(+HpN9sv0}WGlGD;PIpK<NNuGfZZpEYdD!~fJI4PHt*W8M)aw$us9a9WT
z(H!GfgO%aR&;%h6Y0_)wX;*;uQ^PbN426qC*b%aNNv}huON?YKI;O}15xx|1glLz8
zVc20BR}I=Ok_EctrQKA3rYelYDVNpZ%9V=C$x&V~QR6k2&Q|e-q}`~2iYf<AcrXDf
zzyzpJSshe>i4GN0GoV5#YvoF@14Rdm$r)fV<DT&V3;(PSRK#aHRODtrg(Ou#h3=j4
z%=#*#4k~Iq0CLNW%1SH{1QkovV^HysTmThy`LXgtWr19x=D`FQ7+oqaOOKWN<U>%g
zHo7+cbo@#FCs0wmSD9DutAGMnEN51M#j>-({mB31UJWb<pC;E*t6-v0T9)nrpSx;<
z`^o*p^8f_QtB<K==MwuD>OOe~O#D!(tAZFQ#Y-_MT26>@w*V@lWpL7AVrsm@1gJ<z
zF;D>}Kt-$=i~x%i7iW_lC^}e-L`IOiF^+$Zz(j|NZ^k=R_#&WU0{c=OCO}1!kKtUT
z1b&JsIU%JbAc!2JQWXwN@SuWYDB4N!8l$?EOIa|GW>^Pue$}G|!9;OLAOzBD1r`Cg
z>!$z(uyBMffeMFg7wM9<cq!|cBEmzUf_B+?2Qv(OLX=&!7RiE_<|sEsRUG(yURM2}
z!sT>Q9JNKfrd~SM?d!xU@mk%|Uuqk6#Kh0Ry>Khkl;3Et)iv8{k!TR@@Lpsu{I;-H
z-Vs-;Yqf^H7ip*VQmxE>;ca1;-r<_geNQX&*0Y!T8JChBaaU=|4Sl8hSl?scdUm*V
z{cp9^>KjMX`I=g>H;5+$9#>u0`>SWwLG42AqJF7*mh2}69hT~#c2Pf>=}z@z&N%xV
z#k1s4?NZfV{VLTx-ks|8bhD?qOSt{niSF^Qu5@4N2^!tK9+PrfJR=(mJ(=D@_mz{u
zp5STDM4xfm37efFy34(V)8a+qg6$H~r=5|{s<xWFN>=eW`$b}lc;k3Yt=l(Hi`VKK
zZA;&<ZxJsYKL_#R#c)&JRo-Z8wsrd|D)BzD7kOLQM=e&Xf2*z4_9E|5i&p06seM#p
zkKN(+JwFFqp}kZawb&8&%DYNKU#qQD_t<^UThF?_fpc#hyL8jJVqdd0PzgM++F$L{
z&Z-w`mg+_QEZOJiCoISfYQ>Woym{)3(?s_<aOX!YzDgO!yT^MyMt3)R3GXyy>mE0t
z7Cpg}sD%mFtJ9*fa5B@A>Avz6YH^zDW=*smHM7}qsVejqE)o{|1zVqHQqIUWTx;#9
z1RiMr^U3SI*N>z9`=3u@ei700%m1?cyYHI+dh+@2|B(BK=l}7C8=pT{z7YNqv;X}3
lJLQYa^Z)xd^>@$z_rJdUr-eWL_dkC(_r>t@Kbf|E|NmpTCkX%m

diff --git a/render_povray/icons/pov.add.polytocircle.dat b/render_povray/icons/pov.add.polygontocircle.dat
similarity index 69%
rename from render_povray/icons/pov.add.polytocircle.dat
rename to render_povray/icons/pov.add.polygontocircle.dat
index bc8df737d6370ea6676ebe2187abdcd5024aff9a..9b47d16c077a19721f4377429510787a6cf1f09a 100644
GIT binary patch
delta 860
zcmb_aOKTcY6sC(Jbk(gdtk^r;8D+>YI&>BT^)(=bK~fhoBoQBVCKHmVn+OuFxNzlA
zo3V)xf}ybi2}m#zqa^z*`ddnVLeF%SAJD};_ngCb&iTG`?=ybpx(Tx2*TJPg(6N&^
zSK<@5mU48~x8qH-DWn8u>^+)uDzoL!`X<bce;TTyJ$J=b!aLrgC&kBuGA5|)Y$Jmz
zdiLCPRB;_cWV%eKd%z3cL_N1fS=7igz+?Ivx+KLY7h{*gBfAnaB%}nkpzSS;+T>)M
zLb4EJ<G}&G^p5ET|A5y)g6cqTB|frGT#i<mtS=4N6q>QOfqSGfnb4Mh!d&}q{8gZ_
z=PuzKz2i;svJi(_;%qw&s)UQ4I;sM_HqbR)bM78JU=tD3W%KK~N1ic#>Am(P#e@{)
zmclyt$Yw%mRtZc)H~!lI4CG}j3rR7?4)Dlx={*mR>5E7{qB1$U9L~`td=Z&2KR;R2
z0Cx)|Jt1X6OJOChrJgp*_mBF@SiZ>gwD0Pf+E;!=hcQDtAnK7;R`lq;Vra#@u9lTu
zxfnYS+sn#Fwm+#%Ih>E=V9=PJFx|pofuO@e2^*9_Rl%#mZVn4|WM|<aP`3cf2WujW
zvPoo55>i4+in1UJDLx^k_)RV$ZE|TgA*Deev0182N&j^+)YsBl8tT)~gqeoyd&aJ%
z89RqRG3Vf)NDChJ0&31w(4!1vW4Wh&13i7k0zHPNs|O&U7}LT3dQ{ApmHd}onKUak
zQX@^XcG@7#{RTu$QnRC;b`@{!H%LbXYiJcn60N5Jtn;r(BLFgMJq<8T(%EmQEkgqg
zpiUcCEz+sf42@`atv7$(T4~3J|9IY86~{;Gvt#2`&u#0)@%vTI#%#Qb?fKhxFOKxP
EzkIzhod5s;

delta 358
zcmWlVv1`IW6vk;wL0ahCHL*DYA&`RL5D3*v(9^*TbxDv*hARmf94$E9;NTWY4WSt<
zLYCS=K}AWjbad=r(!Zg1Jl^j;-uu4gJ3pS~H<PAhDXl<2TrgtvrHG^?2OmLNUrGr!
zVcY0xC;Q-J9*qM+35`?oHA~4m^9AH3aG7fad?_U>kb|Z4$y^BzF%?4WNxl@+L(DlA
zLp&BI>^c$nj|y`k!E-9aVuE8attXiH&(lFPh<Xn3drrqDe#f>D@hxORTQMPQwGCE!
zS8MCAX*Kxd?5ig1t6X6#L)k(@*##D|kPC?aJ<Ysrw2d<F;N)N}7icYSlmcy(om#@3
z`gGJvc+`qUrL%5UIxFL-RYuh+VF|ggKqou%Due5CP#~VfaK^HTDa&eqdG&X_@NOR#
UUhRJIQ`7jqx9?tW7=ADQ0aUq%#Q*>R

diff --git a/render_povray/icons/pov.add.prism.dat b/render_povray/icons/pov.add.prism.dat
index 0459bdd3ac1d92e5be9d76caf247f49c39c39b1a..ce184e0c306d727b6cda2cd968e3b3b326e423bf 100644
GIT binary patch
delta 58
zcmbQIG*4-QtA4zjd6dk=j>_bT$rC$PCvUB-ikm3CBCaexxysFaVn<pnP^M#I^6KQO
OxLE0l(i=-w2><}eU>KkP

delta 58
zcmbQIG*4-QtA3`NW0cy&NtL-1b0<z(ox8Q8Dr2JRij1<%<Q6x_iIdVgfHIRN=C01I
O%7|5+sJgLal>h+a))|rj

diff --git a/render_povray/icons/pov.add.rainbow.dat b/render_povray/icons/pov.add.rainbow.dat
index fd72b4340beccf2ff3d3850bec58c4801f493d1d..c59f371a2308c850e69c0613a707cbc6f8fafc23 100644
GIT binary patch
delta 1759
zcmWNNZEu=v7>4(0KjZl$3rQ7F3I!46CAkHZAP_1dPA1qkci*&)nXAScUm9y{OzKP%
z*RENUcFB@uAD&P9OV4vBeBnB|j`O(qyZd(+N_ghofB(IE*QdwKi0fwhxt=)0XNf6s
zDs<#CWk5~RW9BnA<omgHwkvhyAvq-`=`VXDG`rw?VpqD}nPIa8R8EC6rB4s2G1JX_
z=K8rIj*`Tw(9U+`0W~Bi>CfDky&>PuUhrLMNDlESB);tR=@EwpnVxvPbB)a~s6gRV
z7;$s%MnE%V0H(w;xlElYm)f~LW)}1u!6zu`$UXH^o1~Yi1wG_%gb9)vfSx)_++oWk
z)X$W2eZ-BKIoA{Wxoc%g%o5Aw8QQzl#>^zWpdqs4Ct^qLN<DRwMn$P1KSIUmT^xW}
zVusyeP>0HyawDvSrHo|sk+Do|@HKU=cdfQP=kD2+aIMTya;f#ruC<`o)IB>9m+~CR
zjEugyOm46Z4xMwoZQlrUZY2x=($SYNg(CbMEq1Lrx1jIYYh@zN!KKzy`{sgPrq<L%
zyg|i1bpTLt@($Zz(1H57zLX#2bzx*Itf{-Eo`^@fZTI|6Z6$285AsxBp`>dKy`F#1
zKGNIl9IOi~Br|n~-Uk1UJrOW~ZM#!j$}8bP9vM@8VXdhR{zSCxuGRBb!acjqPW3rh
z8C|Py4!wJJO+C_cutddu6K%K~e1m<*5XfoU>%ueGqS?y34Ib$?@|Eq>uA+<hL4L_U
zgN3zmR!C+XUPasNEBlgP8Cz&<oFV#r96k|0u{RR7I<<><U3ie6!PH$?EANqhBHqYO
zt><4w5Artql1Edt)$@nmINW9*=~s4TtP5LX=uO?*;EDK-{ls7k2A$eRt}gmHk8(MO
zX=M+UNZ#Km6Xish2_=-!6&||WK5nPWM8rpOC<S|duEJMHhq2Q~0#pta6GJPBk6f6k
ziyr0EF4xK)35Ux5&OTly!c4@6l1~SF6@DMDkajxaTiHks_C9imM0%9NAPyoS4kG!v
zD%e*X#uN-Lk`MGI)XE7hr6%iQU9AF_vqe)V6Gd7}g;G<is|c&uqMbIlB7LYp{Xkc-
zazah|xw=>d4&!pB5XpzifnH9O2`yC@(PgU3Rd`!06Lz{thf*XrwF>X&5Gn0228TF^
zq+%u_!F|OwEZyKV83;|SWj8D}DN!O`1-|JTrceT!tfdrIqUvhH@=aT`^b#o2Kv38N
z9Xc&r#MGpS?<+{(Fp!Rh>JIdlttPdUL{&ju^-WW-MMO#!X@#w;O|4<s;=W?(MMxVQ
z5D*BV5D1|x-ExZ-5adEmZdh@x?u$4_vxI9@Jj+FzhFp*+KFiU*S@$a*lCglHusNB}
zwrm*0wLMJ4S;ErMVJ<*Aa-m_h?6@Z462;N3;hPmtlS=>?5(O2O&-!M=s{18?d@O|p
zLQX~?2L=d)P(`a001x2}2Gsp1t~FVLM33b<VJVOcqQe^;P31|p;?<f?2n^t%S(X7*
zKCZ!56yq365M<G^+)|K3I=oT$<60E61P5u7wcLv5gmM8GqQlXAmdaPWx?gJo16}6v
z*__M(1PWja$Mz6vYrGYI*tfyg#$Ei%{z?7LT*QOp%adpDOL;4<yl>U7jjL#OG&p|A
z|IYkUHqH<GTXh`X#j~R~@(=bW1y3*H%ag6~4B%VwHduLI8?P*iQWx=6G&p_+FZo}}
z%G)^Ks#noCoE^R7U)kT8jk7g=*yHdvxQpM&pV%J^p2F5e3_LZK8VuOAiDIq!>7;s0
zkXYtJsuYxu)R;Fi)m@upcN8q+gw^Bnk?2T`OqI$L#T`+JVtD%L<O4>KA5y?81!~Ly
z-q^Kk%_#nKLXaejWlDh)mXCP!e==2)rSdz96V{s5W6=Sg)S&VVAc~07f~Oxa9Fe~N
J`~Lku{{s|~=T86t

delta 1759
zcmWmA{YxVI7YFeD>HZA^tBx`>Im65_51}$L;4}%NW)Ap}Fen-MfVtVawb{ev16x_h
zx+xYugoz>KO5oPU2DPxT><{;+`!C(^`TjWPe9r5<&-@$sH-Hja<jsHoy?N7|>yTGe
zoo;dszHRGUdiW={CLT*IqDyqh&s3Xfa;HpPsEKWR5APCR=C7c3#x(f4aQU{6^({#H
z#2!n{xfZ;y)1Rp(*S0~&@`*iVYGR9M+q=YP>dSnaIfc2p(6+a2J%oRmZ_Zs&P@o(9
z<=acFk0A-MpV%vEKwq;^TWGm@_{chP982f&iQFNF<N#uJh0BGS*zlaoU1H=Il5OUi
z?Fx0F<!X5PmRoFOMc!lSM82Xr@V>z}xyywf-nWdb$5Kr^2d7I6NdzgGE?iX?8lElz
ziyUp{in?aun{T=LmOgfiAup1SrE7M~jYJ3|UnwKU)HWeb<hpvQ4d^>+4EO*#=W^3m
zSBK<;xTCtlNE|?zE2ZfhS*O_4hO`s;RJ&#e^q6b8AdWmj4rtj<V6d(Z=plJWT`qKm
zf$Lmuc$&T;IdV*hu5itc;L?^07F%zzDTXxUJ&{M^y*L4WsP_C5;t_u!Pqjv{78-Li
z=3eZ{W9Zb?w%!QdQ4i#d8Mr2%F@)*)+xpb@9ec#lGCI|2p^-S|?!_ymClA#LF||G7
zr&?WY1jpPRHDh}6z%^FtYSY)&@2Cm!Kn`3ZSlsl%gnw$AV&5@<v{P;3d2-L7Hr8*#
z59F);#aRnoM9*UP;xqr`9;#Dq3}HIPMRdlzIG_2kGDFH#Ys2%7@reJ#UhODe3!TL#
zo_q1h-SZFCvHn0l;;;5vs1dw~-itHlnTIM&HG*xuW6YQb^2Iq;CZ3tn)_eY&@FV^m
z`-!1ATCRmYP!+z+y(5ZLfjnT2q^h|2Hj8I1yY#LAow*W&iqxhpPh{~bQx$iG_w!}0
z#FXp>GEe-?BI!ulz+lAkf!d`j{5zsNSELS@zu6;c^X;ZBi|^7^W>+ZBy`L{Jo3@fY
zPgI!$klxRKpg^*}BMKxqAb|r?ncH%0E)>WOcAc(@hw>hhvzD|YWv%cP&z7r5<%JD4
zi?5Ms$F8s^S3JNj<b^!3POp(i67n9(DJ*MAS<767-*OemBDKL*#UtquoGhMpRCu_|
zQ>02vUdZBkVvXDts^XqpV#*vy5NDkR2c#6X0TS9=C@ML5olc7;w<jNHJ8H_BAmX+y
zSJ_uoHrR|SEv6kwDnV2{J8IdN7jkmOwMLp;k~)-;cA&+vlr?VKT!8q>I=#WBVcj7}
zDQnu1Ahui;PuaJ@<^_-(Yh;qDc=qHSH7{%~<m5F3c}xx<FZw?OR8G$M*Hn{R^+d%T
zbw5-I#%(KP7%wU%J?Dp*%Bm+p7|aS;_Emx<9m1%llBA-d!5nC4c|Wv_#cg3cC&OWW
zlS_-M5d1*f55;W>VudU!WnW28iy4=xBnXtG45sYcQ7gfW3-YK*%H*OVKtKTm)S8+J
zn>z4H$Xy9mW82X%?sqQPbN+&n39owMnxU+aI`4Ou^zCTDFcnjW8WVEs{C)_<t1%4=
z<4g86HRsQSqY%eXD#86wH5SHK5b1a3{3X3$ta?l(uC0&;qw^)b65NiO3S6ceOjHcH
z0a`|J4O<3kro4K8EZ&5_Ms8y-&OeUd^jWNBo~NGNzob|1SpOFI8o7w}lP&X^|4sjr
zrrM9iZ-I_+8|x=u?SHU84wO2Jou_8XlN-HyZ^C2!YvjcVEyr2xBHA*a+|T?kX{=AR
zZ-I+w$LJ@Y`4{JJdaBKoAB!F1CVU%vwg1HaU?_#+XE9Sx#S)Q_JFvLq-Hv`tZJA4U
zh4wl!;cPM$Gn7amu;_RCB!w;*TV^&H*AkIPK<AgdzPMD4q14Be7hAG>9j2ZMr(z+u
zp#&DUqt)2Q)RNurROn2&U}Td9{GUj`@6>spR4}%qTV`A{^+ZJHL+&LnIB^7y2GaL`
I{`$-IKg_yoMgRZ+

diff --git a/render_povray/icons/pov.add.sphere.dat b/render_povray/icons/pov.add.sphere.dat
index b202e5cae2340779afa186f587810b2e0a1dcc83..c820fe021ada63cd68f6ff5815e009d26b567641 100644
GIT binary patch
delta 2399
zcmX|DZ%^XR7Jhxdg)3nr2{CRA3E>TYwic;{<&XAm1#K&_jY<Jq1TqR;6cEr-ORY<B
z!|Lkpnl&M-`}-dE&HX_4%)Yp3&Ut3eIa3<u%=65E{qM&sLPLtC^afL8X*ROij;zV8
zAPV*te){eR+b^jH>TXle^6l)PEFTm(z7P^a`2go%C*nR1<?Fl6)~I2fU778RbN!S{
zbL=K-vvzvIa{it0R>byOVKZR+&4A&PmUex@a>>2tF3fk_m+s!enda)lP;r%T=uLnA
z%=NnQn)vhY$VMdbJ|0VuNs^jTv*dH4NAyC&6`2rM3ZZf2nzX_KxfR(7KQy0C9zUg;
z@sk8PQp(DaTrf*|d8Ay`EERltRMPuqXVlR<qqD1yUYk=~Dp+B@{6=wU<u~eFW3DxK
z=4#D#-kiBKrLE|vZKbao&AzIG*VR$QtebT_0)w1>dU38P7w5eJ{_i?EQabwC6@q`E
zVIlm$+!<K9*|zkdVe~N?{UHo4VSWkYX&3UEr8(NbY(F#~KTW&$$KM*$?zhH6bK3n<
zpCTJg(f#qXdsB64HgalT>Nqm(TG|u+oS3lBiCJ>O&XO;g3HuNGho6AGiW9KkvLpSL
zXNn`eijFklXUcQkk)G>MCyoSlQ2CPihn=urV~><JNk39g^d#-9jiX;<7{RlX^b>un
zd_Ei(r<Ge}T)eMND~>d+jEj!+Si7$}($mQg<Vc!~<Q9QsM6}@P5q28-hp^cmF<i0f
z;mS2>)9|Em#%6KG<~y>@+fu)1OV{#HE7^R%*paVgt2`_XOE`Sy>bTKd2igNS;)S;r
zyzuHvt)RYS_*y~3X9jv9jJ=Q<u)+p-Bh(8uU<>go6hw3V`JwC5J#<-b4G%`cW4(1e
z=Az@7dadQD_u-xGe|Y!PXZy9EzUROl@f?_izLT@iZ}QjbZ}OF%Q5a{`EB%^yiNDe>
z@fr0T_)gvtj>lRy7pGq6>*ZPK*XnoDT*R-xBgW9{2WnZ_Z9)?&(&>?cl)?Zh$SUb2
z9a#!=NDX{R$I-%I|NKbV*UoXFbcz@4HuH5!&DVD-k}4U3F7^dIJIJ>sL+tOti0KL%
z(@jFJryEQc`}Jd{3x=33gFe<duw0Epx!QJ>LmYx?Y>+FMlCk&qefc(-j3vT+ED@)}
ze4N%&R76ZsOe`WYv0OB}#>BE~*>q%s$b?xULngy4Joq?@xID~<Suz<_2$_iyGF}ru
zq3TSGsz((j25ZxsEEC&g6KIpo@(GkcSw2<E@>wZU%@wx}H<c`(sX|1`6`2ZC$!SFh
zNlYbEWnu{wKM_kpS&#*pm&6h;ar{9MBPJe>Ez2CsrB&icR-xs%G*8O}PYa|7tiaRQ
zs|3y?$R5eK8rO1cVW9+FQ394ha~Khv{@NTKt3j=>pau(}UEn4)i2c4-4eon!G#}XY
zVYb@|=L5o9$t8LuAdNk@<l1wK9>$yX2+Lq^^NSesi*aB29p_KKW4xR{8lnOpNGcHb
ztrHm6i3m-QBt?*rS$!XmBqB6P0csGYR!NFljfOsuoS*Ru%fed`UdQ>jBI^W)wyXL2
zc6Fzcujku>CY=fzhjyD7cbj-m3j?vAZ||Q&TbX;%S|uGv_s?;ILGPT7DmPWLep9uP
z2@GOsM$^*Fwti~1jXuzr(ZAGtgNt*c-y2+FqSP<-4n!<XKQ)?06EDg|1(K(8QJ&7O
z@idptrZIx&H(3rWn+D#;V>VaJN`jn~vOF&d<*mb95jL}oqcDd}&GH;?)7RuUI*vMP
zHtlF@le7)=Gx;kS6W#`XawJ=Fq;c_D#&|7{i=Pjz^0;V~uVtv?vAq0U12kguoIfAf
z3ETd0#QML-l4CAf%leYFZ2Jx0rQ7iI77Sp33-5*3T-28?ym)RcYlcq;Rl<tK`GsYy
zPx(b@DZeOs_PwG9lxiWrxbKyg3JZ7+GPSVh?zsE&m+sdMV1`6w15QU`E0T+@6S*i8
zTPK)Uio(0TPNXQjv9ScT`hkqo7~^zg<9(dSypNL<nFQ40WHLp?=?~=B*f<I#5*<hG
zh&$pt`AB^SGJ2watpfEVW)x6R5`J*wCG!%0$;^^>#3S`J_G|Sy@kBq-xElIC2xJ^=
zfo}zSA*`4NXeNw-oQ;qb9<Es7YjU`vg2cl)2_iyM2{8m+4x*|CWde>6Bm{CNEUth-
zd>k1^uStQ-MYkdk%^&FLB-KoQil12J&xfPR=fhj&ri$^VdaFz;AY2vD3Uprul{kK^
z{XpLu7{4`spob<1TxE*B)Q2UjJgN*!ql&Jn9l2lZtNr4zq^lTp6~QG$Q#83Fx8ZW4
zfiL6p49X7Y8mJZIMiEN~MYx<4;ZmYvJW#g|h4M~CD2t^XAeK$#V|`QEI^3@2YMaV7
zP)?@0ehe&A0k%X;<zqe7OrUfllR&9jy0NL`Y9E1k#ge4nS3!0Wd<5|K`f<9Esxb&J
ty;Z){o!YJ9)NZQV)vd#w3VgZ>AEyB8%gU8HDvvs&*68fd=YRcY{(s~c>Mj5P

delta 2447
zcmX|DTW{K07Ji<W`3-3psvw~#RfL3ifkX}$$50AI#E7Vd1e_wa0UL~K61$`k1PBl=
z7L6gnt%y)^(x~mEN>ZJdqa%$p(&#+R5197+ky(2lMq6urd#|;(x$L#R&;Q<P(x-%4
z<!gMWc`57aM4r$sR71zks<X57$u=h@b|mmZR$<l5QL-tb0+(h4SJstrr0g-`r7b0A
zq^wf!zZ*18M@PN-;4mFZ(FuA&k3$BXvhS_!tWWG`Ya_?RK5}R_ZY67DS4-xcnO{6H
z_syN9hHC1<R5Fzy)OylgY?vM+kAd$|Z^Rq$`JV@<pr5=WzXiVrIz-1c+z<$~A-FEq
zb@$mC>)u)4Tf0_n_kVdCRl@rbxx$zE3gUaEw%C_Wm4OUi?3dczUaNo7ZuJ|dCv9ax
zGRa{4qWDBIsp1oP;oU-gp<${ow4O9fHNM3U4qJRz(3Gw)1V0qs7e~@a!ZS_5xxrzt
zUgLZ9P80ukC+#YK(r%nq;a_++sNw<ZX;W{Hj`a4ht2HocjbXRj>%+Pa^H~k@s;*Ae
z=IH2J`Q`1bHm_VA%xYH$*UGH+QM!~T@})el%xdT2v^<fg<&P4M%xb!NOMML(^w+?*
zph15N-iHnPKKvN@PJ>0i(=ZqV>>+J{-6Mm&M|Zrz!kRUhJN}DguwSIxeS?L4jEWC`
zrw#hjcTF0hYx0&dLdNFQbLqnfo;E_a)Cc~eFy&^&5B!vyi?gD^&WcmcV6V$_(O_@n
z>$1V#?%&7;TSHY;C#tAQ)M0dpai|9C3Ed%v8)Is?p}WTvj1bOD=(-B2G1Ee0X3U;)
zV^&96Zp@r=Eu^Efyq42)INC6sn5zpXP%yYOd)8xT&pKE+b`DlF+p$x#jT{{p#*S;`
zI9t=eYp#y#9b76tRdrSux{E_o-#j$wPc;ih&7wb@T1Lz2)H1W`t267>vx)uc*(ck?
z{>k>mF(<w_?p!zSJJ-GYv-jTpK;4lT@5l%0G4SAjpdS2p<QK<{drnL(`s!$TW}Vpe
z)jQW`?~Qx3j2}NIE};5(q0DC$C`+C@tngJ{Xjb{^pwuq4s{`q@sty!@QX7YbX1-qG
z^L4zDJH&^wN=8CLMtWUD0_w8uY=dnlo0%ij%{Fo{+pe+db}b6kzjXizK33VT#VT=m
zyB3#UNhz>oIVC5{yJAXC$*dX&!#Tc(x;c2B%C<8{RPcGg%RCQ+DK8VIqNFb!CF4F{
zI_^t)5}R>fVlxr)M#!*-Cc{+FL&Jau^`-G{FXN%1NqG{*eI&wX!uvrf?h8tOKJJ6F
zp#%+{j>ri*!9-+0CR$D~2{tAsxgE8@@Cha+LWoUr+r{l-l7k4lU5trwUqlY?qugGe
zMJ!6QD3@l_Oo79QsVGH@C`G43GEqS?lw3NLrUW8Qv2Nf9K<s4#=c|ydAiNvzP0_+q
z396z5yrWhZ@T3Zape*3|8>g@Y=E?$Fy>Vinx4v=atvH%-WNn!L_SP~E_9-%DEeL2N
zXGW%+IcwRrCN1nL*wf51#?10d+t#y`ee2n_HD&j^f{utg=y++15g22Hk0RZEl?3N{
z;r01}?<ha%^W*Rfub%{SMcgU-wv}CFpJw54Df`ZPj7Z75Vn*5(Ul%h{=7?3fLsm`6
zSp{QO!FMV&vyIGAzFuncy&P0lsg0xgI<BwG5qJ2$crK2lb8#Y%Bw&4A)f8PF9Ssgg
zM_Qv-*BbqHr-`xC>;rY4wEOK7i0JCzP*XHz1Vohdu<kUK^rWf8W|~Te;FbZwGYL8k
zmI%RR0H)|9mta|xU=vK5-OKM3k{pZn@;I7?cv{5?W=!essb}gjHpi5)X@tOofM5|Z
zm@%-F!H!vjopL(DsG})&Q8>#_xwE{EU>}18=YMB_MaE3Zo^kB0P3#xzXaDzDghtEy
z>R?4*o!B*7->g|WOPWO;E%&TF>u7nf(zD`#zN*;<pi@}mQg(I~D^_NiTgfbEEqQC!
zk~0fSndQ8dTM?G<x&-T-`NZ5^?3*7W5B|r1Hv-)e*jZ0{Vnost_r-{~FG}KDj}cK4
zU+nY13va|7rT|qhv{1z7C&NC!pQM5qsbCcPEaJZOO+7#_o{RN4F(+=^*W?Y5(Jl4a
zdq;i^02TSa1>plD_u&WsefV2&PF$0hzR%vTfm`a9!n>jFoj}IUWAMk$jtgt31}X}3
zAZN{Wwx$j@&enAIaN`ZAJ~WE63N4d$2`((N&<BJ-yn#6D-djT(duv&UU#w3(x|?+;
zJv-~y%8h)xA624n!~19Xi^BWjMd1T~E@C_vKk&2SM`>1^iy&npXhr3^d?Q~SV7xj2
z)lja~gIV!X{wQg=v;6y_mU~|u3N3WXb%j$-%MAsLL!k<<B{jZ=TBrrDD}cNR{fxC9
z`WmPe<c3E%U<RSUiHH|4^1@DGFaNr@mq)qRz$*;@N@Dn(!mgMsGyE<vPE3qd)R?$k
zjLSfn#aB{PiO5?A5jk4kI$-!@`4uMK91`Z@ymBeSmjM2bRkjYI<+vQH#NhXRl%{3S
z#PYegEAA9t7h9++wBRL#7epkT%6+ll>eu15<F}vx`1AKafBX4Qz+Zp<@#9}VzyJ8-
U$FD!{|33Z8x5dGK{`$B70*qfMJ^%m!

diff --git a/render_povray/icons/pov.add.spheresweep.dat b/render_povray/icons/pov.add.spheresweep.dat
index 67551166b7346084a83ffa271431af4243b6fa53..52a9f5c0e388dc25b358d7b0262842b0339a67ce 100644
GIT binary patch
delta 1681
zcmWlYOK%!y7=>5uCj9|*8Wm$<518OkY!hE&92~}Ad$<$84CZDJgyCvtf(>SX;4AnF
za|2V`6IE^(l`2KuR9UpYA^R@7>`$orvUuNf^l3)t=+XE6WBL=lT4BVMn^#?<Xu39t
zD*}48ESh-prBOh6%f%bs%&nh;<FIoJx6WEy>m0_~TNrC)olQGyXI)Jv>tsDmH|yfO
zEidcgd@Voc<^AV4=My$Bws^k~xC#rKLZGvK6BP-bxZM#=SIgH10lhR{E%S`|;?1Rz
zx16IFZy*em!&;ya2Flu-gE(h%5a(*TA<onCHr){BZ+SW2Io|T~{)?@1T-dw{Uu+41
zn<&Hux_D>1v)#pcqKo%}Jkbl@h9ElBkMKl4(vQK8-R=y^FkM0<qP6X2q#ms$PKmQv
zEqRhS-KnOIk|zX{X{3(gm25rJNR;zTww@^Mg1*};?CqA5bb&7Hkp*hEK$AHtL+z4j
znxryhl8)14Dn`dsaWYKDQZW*zLu8orQ2`Ps-ISN~kq*jDx=D;;?4*OVP#YwcGEo-N
zk}}e0%9LKBjVWVh;k_Zfl$|LqW)^bOd-K_uUETgnZd#PH%8s%t=Txk+ui(cy)!nF|
z=Ev{Hgy(|#K_zO0=aO2S5H*rUd?8MJ)QT@YzL0dH_M=vs5`U0%l3vtFQx7kNNk-D&
z&kA7g=Y;9|*}DZ^e?Rx*BKPucp})k<{<wH+VCVWvz1N-jTSM2_UhKV&)`+w1X0#Rq
zUa{IvHF>gAC79F^!Ne<>M!b?J1E+GLlxGs9cfjf09*}||3&5!Wq;|<1nFda2AjObm
zG8G3@F&cmYt`H5lr~m--QC<M%rUt+qlpTO!)COe-TEHu1qETu<3wWiM-WzD3Ra|^;
z$j<D|7Z-EW`=GnJl74@N(w3)6dRkM_m8Wh;`U+0fSKN*A<9DOLOU>MmJt&3e56TA>
zY!zVBNNT_~A!$Co0Bl+j-~qZH!~r@`NiR%_IzTriOai%SVd{R4*9+5k3*1Xy59DUK
zm;EJnj+?zTbmrN)-s|>aXTEE^dDUJNSFQ~=uN;Ln%ARx5>qSSw%@nbcllJUe58TDK
zWqZ}L@2fcKZ_9qh)$mm|tDYl2gV(&ro7G^=e~Q<(>VdOhEmRLSx9Z_~s2Qrm#chV`
zl(wYb)zO;r6s0YyE8zB347dZNHUVz5t^ucLsF~3ZBZuQP#i9II^<m^>m^oD*%1?${
z$eHqN_#9~=E!ib<j$F#FkW1uRc8y#iZP^WSjj*yda)a>09c3GF7p(_a$+K^-S`Xfq
z9d&!vS8+8QbwA@day2%q-eb=Z4t2f9!CK(Vf4T*A17{(q8-%LSW(Zyz+@NMr%`n=E
zZeLZKV6+EFT?0DWP=hKC#~bPoBgd-4@k9Bk@>q2;e5O2A!X8u|Zpomk>=LS8%C4d6
zwX6+Q+cFlavNC?S4MjzHM=8p?@K(C=+YwGhj3u6t@`ySjC&q9sjzrWxy;sBFTF-)O
z(x-PW+h#orb`!P)Zf$J^oXu>ptzqs}D`wm9p>E8*fusHn-$ob>Zs6E98r=wEag@Ne
zt;y9S7PqC%X=~D+v*c~*{CBx$nIU2<B2PSvs3A;_YrPsA@#$TYUX5qLHtU*nE@MkJ
zX4Y<6TLH6Xwt(7Jt!4}6Mj;d;{pcVv2%%v#iUuK)K%-cEHHi||q&dBsw52V1bK0I;
zqbzwxVZFFUIcco4Ud(-;m61<jAby~OogT`4pB~B#<(>_6P%k@_d#cDi&3~5}`cnFX
z{+#)o`8EIByU&@wB40{hO22;?oc%%nn*U_`+VmCHk3Gg7Q;(^iGE!E`-evl!eyW%3
z$9l<L0(PRe(~tFIcM&Npg@1}X#(s(aoP3PIqz3;<S@=FCg$I7%KK=b<^_%v;PZXcV
MzBN2)|2Lxe9}U5sx&QzG

delta 1643
zcmXZb$&T7s6b9f`_a^-S*`*SxltiM+MPY1X3}xogfUzkXGtUs)44$rsDh!k&Wz}ja
zkCGSYr|ES+LLa1-b^dd7?^#`a{=4)~@vnT?yHRfB57HOzR`|$AN?*C#I+ndJeJ#h*
zvFv@GqRDiOWeA2QQ!Jh#7^;G2=mN<_i&Q1fV^xyF#rtNQ$Hj<rC*X3V7m@H@WEkn;
z!^ku;#3y%)$P}O7t?w4`)xCPRj&HE-y-IGeEum52{^3Aw6Gqge_O$tECyb<xyR^wT
zaW8475D_p=+NEAo=*gvzX_xhw@X5`1=@aW^J@%Y|%%8L8oL4;O&v~zWDxM2oH7uV>
zZv6-yHD2n+3Tg$7m#V{it)OPL5%1Ma(QYH|5OxgFZtR}-9iypB`t!c2O5Y^CwU$@X
zx7Jdg%PW4?oyc>3+#Pl&?Sazk4%><%E4{X)bQD<-dxFvt_+G0gh>bqiYjtXaTA%Bb
zhvh--xiBgY%W`g<9~HaoBs<P4=_&Mnat3`w&cgH{F(Q}AMQTE<>1A?;ZxU;I5mTv6
zV)dv|(4#sD{b5Jyq>k;09l65}#GW`{CUGE4%py#}#%$6e5ay6J={_LRA^itLAu@=%
z6rzqXc)-UH_Y%k0iSQC3ekOhTL|hVnGCY$P+D~2*7bajX^jj)OU6{A0ApMqlV}qx+
zr%M)+z2pM+l06rK%q4d&`Y@eJK1^Z7&!0+R)ys#KW6dpu)z`XPJl0+~RC=uk4X6CV
z`AxeLG`yBo^_wnl*1VS6HtH^q1cP(Kwj&;zNN_s)mLuBFySCG@<gH*oTU{L{OVK5(
zYxXsm%z>tueRHVx&4D>mhvv{6t7CIyO|+>wwx-(Ln%FaaX-)0fezRRmH~m}Xv#A-X
zqe@@+2zSdz>R9P(9V-Lvax71!V_}|R!8XgJSOSVJ;2EkCE#euH!>Z9@oWE~kRb0Fi
z?why>x+Po&-97xOdx(Q>uss3Y3w$15f$dd%bFaoXgobU&?ZW}vQv0a+a7Y-BcGRRz
z+<ml@cHF~V#vy{ZmkJT}N(5=JO}#$3Oh_MDu+4-Su$?|-z3iDiXT2O4hq@Z~3Shk8
zmE3Ara?7ZGETak-ub`^a3aU=c;=P(xH`}OgatG1o4vk&MXzZF-+B)EdYOR6x8gQ>#
z%aEUe?HTBv@Z<IXY!5(pudRS?xh)AI*cL&zAn;<N*Ag3@tL<6`Y<J4fg&~xj8x`eT
zmmTN3tdgE&m2^KjW&6oNVwN6}i_|qI#4<S{X84+(;fvTNv52i6RciIP38NYaS08i|
z3IpTV?rI!6fN>BG#!buy<2HuCIKtcq2ZV!e5FSJk6+~YjT@daiT<S#lw3h(eNuR#Z
z0Rya=AQdoxIvu0{HLwQGz#2Gb0`^?+vtf_{&pAH}p7VY#tay1pA6DIhmj};9xA0m=
zCAav(Ib~D|8g|7g`%SBASG<;4v#Ks{)Xf@r=8QTL4o!n|#C_|~bUM5CzGXjeg<ac{
z_0CoR&JuuDG+CE`GjwyH_O(y}&sWhSbqu1%+Eg8b=eahurutHw+q3OjU)nQ!zTYUH
zr7yxq=|=f{P5DFbrgzi-B;A1XPyO5d#-1ChJ$Dx1eSubnidJY1xkekv4f+Q8%~7v^
zgRPm`&mSlMyZ!#V?H*pW@AjW?@bd?HyB?l=_wcL#d+|5MSa?gMTe_j|)%|+EG#19f
loZIlBxwrIGr2l`vuwcVjT(_<}Hr)RD_nr91ul{@X-G2(?K8pYV

diff --git a/render_povray/model_all.py b/render_povray/model_all.py
new file mode 100644
index 000000000..412894a49
--- /dev/null
+++ b/render_povray/model_all.py
@@ -0,0 +1,915 @@
+# 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 mesh_eval_exported := model_poly_topology.export_mesh(file, ob, povdataname,
+                                                                  material_names_dictionary,
+                                                                  unpacked_images,
+                                                                  tab_level, tab_write, linebreaksinlists):
+                pass # XXX Caution pass may write both proxy mesh and primitive, so propagate a test
+                # for non mesh_eval_exported  or switch back other primitives before meshes
+            else:
+                continue
+
+        # ------------ 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")
diff --git a/render_povray/model_curve_topology.py b/render_povray/model_curve_topology.py
new file mode 100644
index 000000000..121def679
--- /dev/null
+++ b/render_povray/model_curve_topology.py
@@ -0,0 +1,996 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""Translate to POV the control point compounded geometries like polygon
+
+meshes or curve based shapes.
+"""
+
+import bpy
+
+
+# -------- LOFT, ETC.
+
+
+def export_curves(file, ob, tab_write):
+    """write all curves based POV primitives to exported file
+
+    Args:
+        file: The POV file being written
+        ob: The current curve object to export from Blender
+        string_strip_hyphen: Function to clean up names
+        tab_write: Function to write to POV file
+    """
+    from .shading import write_object_material_interior
+    from .render import string_strip_hyphen
+
+    # name_orig = "OB" + ob.name # XXX Unused, check instantiation
+    dataname_orig = "DATA" + ob.data.name
+
+    # name = string_strip_hyphen(bpy.path.clean_name(name_orig)) # XXX Unused, check instantiation
+    dataname = string_strip_hyphen(bpy.path.clean_name(dataname_orig))
+
+    # matrix = global_matrix @ ob.matrix_world # XXX Unused, check instantiation
+    bezier_sweep = False
+    if ob.pov.curveshape == "sphere_sweep":
+        # TODO: Check radius ; shorten lines, may use tab_write() ? > fstrings since py 2.9
+        # inlined spheresweep macro, which itself calls Shapes.inc:
+        file.write('        #include "shapes.inc"\n')
+
+        file.write(
+            "        #macro Shape_Bezierpoints_Sphere_Sweep(_merge_shape, _resolution, _points_array, _radius_array)\n"
+        )
+        file.write("        //input adjusting and inspection\n")
+        file.write("        #if(_resolution <= 1)\n")
+        file.write("            #local res = 1;\n")
+        file.write("        #else\n")
+        file.write("            #local res = int(_resolution);\n")
+        file.write("        #end\n")
+        file.write("        #if(dimensions(_points_array) != 1 | dimensions(_radius_array) != 1)\n")
+        file.write('            #error ""\n')
+        file.write(
+            "        #elseif(div(dimension_size(_points_array,1),4) - dimension_size(_points_array,1)/4 != 0)\n"
+        )
+        file.write('            #error ""\n')
+        file.write(
+            "        #elseif(dimension_size(_points_array,1) != dimension_size(_radius_array,1))\n"
+        )
+        file.write('            #error ""\n')
+        file.write("        #else\n")
+        file.write("            #local n_of_seg = div(dimension_size(_points_array,1), 4);\n")
+        file.write("            #local ctrl_pts_array = array[n_of_seg]\n")
+        file.write("            #local ctrl_rs_array = array[n_of_seg]\n")
+        file.write("            #for(i, 0, n_of_seg-1)\n")
+        file.write(
+            "                #local ctrl_pts_array[i] = array[4] {_points_array[4*i], _points_array[4*i+1], _points_array[4*i+2], _points_array[4*i+3]}\n"
+        )
+        file.write(
+            "                #local ctrl_rs_array[i] = array[4] {abs(_radius_array[4*i]), abs(_radius_array[4*i+1]), abs(_radius_array[4*i+2]), abs(_radius_array[4*i+3])}\n"
+        )
+        file.write("            #end\n")
+        file.write("        #end\n")
+
+        file.write("        //drawing\n")
+        file.write("        #local mockup1 =\n")
+        file.write("        #if(_merge_shape) merge{ #else union{ #end\n")
+        file.write("            #for(i, 0, n_of_seg-1)\n")
+        file.write("                #local has_head = true;\n")
+        file.write("                #if(i = 0)\n")
+        file.write(
+            "                    #if(vlength(ctrl_pts_array[i][0]-ctrl_pts_array[n_of_seg-1][3]) = 0 & ctrl_rs_array[i][0]-ctrl_rs_array[n_of_seg-1][3] <= 0)\n"
+        )
+        file.write("                        #local has_head = false;\n")
+        file.write("                    #end\n")
+        file.write("                #else\n")
+        file.write(
+            "                    #if(vlength(ctrl_pts_array[i][0]-ctrl_pts_array[i-1][3]) = 0 & ctrl_rs_array[i][0]-ctrl_rs_array[i-1][3] <= 0)\n"
+        )
+        file.write("                        #local has_head = false;\n")
+        file.write("                    #end\n")
+        file.write("                #end\n")
+        file.write("                #if(has_head = true)\n")
+        file.write("                    sphere{\n")
+        file.write("                    ctrl_pts_array[i][0], ctrl_rs_array[i][0]\n")
+        file.write("                    }\n")
+        file.write("                #end\n")
+        file.write("                #local para_t = (1/2)/res;\n")
+        file.write(
+            "                #local this_point = ctrl_pts_array[i][0]*pow(1-para_t,3) + ctrl_pts_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_pts_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_pts_array[i][3]*pow(para_t,3);\n"
+        )
+        file.write(
+            "                #local this_radius = ctrl_rs_array[i][0]*pow(1-para_t,3) + ctrl_rs_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_rs_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_rs_array[i][3]*pow(para_t,3);\n"
+        )
+        file.write(
+            "                #if(vlength(this_point-ctrl_pts_array[i][0]) > abs(this_radius-ctrl_rs_array[i][0]))\n"
+        )
+        file.write("                    object{\n")
+        file.write(
+            "                    Connect_Spheres(ctrl_pts_array[i][0], ctrl_rs_array[i][0], this_point, this_radius)\n"
+        )
+        file.write("                    }\n")
+        file.write("                #end\n")
+        file.write("                sphere{\n")
+        file.write("                this_point, this_radius\n")
+        file.write("                }\n")
+        file.write("                #for(j, 1, res-1)\n")
+        file.write("                    #local last_point = this_point;\n")
+        file.write("                    #local last_radius = this_radius;\n")
+        file.write("                    #local para_t = (1/2+j)/res;\n")
+        file.write(
+            "                    #local this_point = ctrl_pts_array[i][0]*pow(1-para_t,3) + ctrl_pts_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_pts_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_pts_array[i][3]*pow(para_t,3);\n"
+        )
+        file.write(
+            "                    #local this_radius = ctrl_rs_array[i][0]*pow(1-para_t,3) + ctrl_rs_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_rs_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_rs_array[i][3]*pow(para_t,3);\n"
+        )
+        file.write(
+            "                    #if(vlength(this_point-last_point) > abs(this_radius-last_radius))\n"
+        )
+        file.write("                        object{\n")
+        file.write(
+            "                        Connect_Spheres(last_point, last_radius, this_point, this_radius)\n"
+        )
+        file.write("                        }\n")
+        file.write("                    #end\n")
+        file.write("                    sphere{\n")
+        file.write("                    this_point, this_radius\n")
+        file.write("                    }\n")
+        file.write("                #end\n")
+        file.write("                #local last_point = this_point;\n")
+        file.write("                #local last_radius = this_radius;\n")
+        file.write("                #local this_point = ctrl_pts_array[i][3];\n")
+        file.write("                #local this_radius = ctrl_rs_array[i][3];\n")
+        file.write(
+            "                #if(vlength(this_point-last_point) > abs(this_radius-last_radius))\n"
+        )
+        file.write("                    object{\n")
+        file.write(
+            "                    Connect_Spheres(last_point, last_radius, this_point, this_radius)\n"
+        )
+        file.write("                    }\n")
+        file.write("                #end\n")
+        file.write("                sphere{\n")
+        file.write("                this_point, this_radius\n")
+        file.write("                }\n")
+        file.write("            #end\n")
+        file.write("        }\n")
+        file.write("        mockup1\n")
+        file.write("        #end\n")
+
+        for spl in ob.data.splines:
+            if spl.type == "BEZIER":
+                bezier_sweep = True
+    if ob.pov.curveshape in ("loft", "birail"):
+        n = 0
+        for spline in ob.data.splines:
+            n += 1
+            tab_write(file, "#declare %s%s=spline {\n" % (dataname, n))
+            tab_write(file, "cubic_spline\n")
+            lp = len(spline.points)
+            delta = 1 / lp
+            d = -delta
+            point = spline.points[lp - 1]
+            x, y, z, w = point.co[:]
+            tab_write(file, "%.6f, <%.6f,%.6f,%.6f>\n" % (d, x, y, z))
+            d += delta
+            for point in spline.points:
+                x, y, z, w = point.co[:]
+                tab_write(file, "%.6f, <%.6f,%.6f,%.6f>\n" % (d, x, y, z))
+                d += delta
+            for i in range(2):
+                point = spline.points[i]
+                x, y, z, w = point.co[:]
+                tab_write(file, "%.6f, <%.6f,%.6f,%.6f>\n" % (d, x, y, z))
+                d += delta
+            tab_write(file, "}\n")
+        if ob.pov.curveshape == "loft":
+            n = len(ob.data.splines)
+            tab_write(file, "#declare %s = array[%s]{\n" % (dataname, (n + 3)))
+            tab_write(file, "spline{%s%s},\n" % (dataname, n))
+            for i in range(n):
+                tab_write(file, "spline{%s%s},\n" % (dataname, (i + 1)))
+            tab_write(file, "spline{%s1},\n" % dataname)
+            tab_write(file, "spline{%s2}\n" % dataname)
+            tab_write(file, "}\n")
+        # Use some of the Meshmaker.inc macro, here inlined
+        file.write("#macro CheckFileName(FileName)\n")
+        file.write("   #local Len=strlen(FileName);\n")
+        file.write("   #if(Len>0)\n")
+        file.write("      #if(file_exists(FileName))\n")
+        file.write("         #if(Len>=4)\n")
+        file.write("            #local Ext=strlwr(substr(FileName,Len-3,4))\n")
+        file.write(
+            '            #if (strcmp(Ext,".obj")=0 | strcmp(Ext,".pcm")=0 | strcmp(Ext,".arr")=0)\n'
+        )
+        file.write("               #local Return=99;\n")
+        file.write("            #else\n")
+        file.write("               #local Return=0;\n")
+        file.write("            #end\n")
+        file.write("         #else\n")
+        file.write("            #local Return=0;\n")
+        file.write("         #end\n")
+        file.write("      #else\n")
+        file.write("         #if(Len>=4)\n")
+        file.write("            #local Ext=strlwr(substr(FileName,Len-3,4))\n")
+        file.write(
+            '            #if (strcmp(Ext,".obj")=0 | strcmp(Ext,".pcm")=0 | strcmp(Ext,".arr")=0)\n'
+        )
+        file.write('               #if (strcmp(Ext,".obj")=0)\n')
+        file.write("                  #local Return=2;\n")
+        file.write("               #end\n")
+        file.write('               #if (strcmp(Ext,".pcm")=0)\n')
+        file.write("                  #local Return=3;\n")
+        file.write("               #end\n")
+        file.write('               #if (strcmp(Ext,".arr")=0)\n')
+        file.write("                  #local Return=4;\n")
+        file.write("               #end\n")
+        file.write("            #else\n")
+        file.write("               #local Return=1;\n")
+        file.write("            #end\n")
+        file.write("         #else\n")
+        file.write("            #local Return=1;\n")
+        file.write("         #end\n")
+        file.write("      #end\n")
+        file.write("   #else\n")
+        file.write("      #local Return=1;\n")
+        file.write("   #end\n")
+        file.write("   (Return)\n")
+        file.write("#end\n")
+
+        file.write("#macro BuildSpline(Arr, SplType)\n")
+        file.write("   #local Ds=dimension_size(Arr,1);\n")
+        file.write("   #local Asc=asc(strupr(SplType));\n")
+        file.write("   #if(Asc!=67 & Asc!=76 & Asc!=81) \n")
+        file.write("      #local Asc=76;\n")
+        file.write(
+            '      #debug "\nWrong spline type defined (C/c/L/l/N/n/Q/q), using default linear_spline\\n"\n'
+        )
+        file.write("   #end\n")
+        file.write("   spline {\n")
+        file.write("      #switch (Asc)\n")
+        file.write("         #case (67) //C  cubic_spline\n")
+        file.write("            cubic_spline\n")
+        file.write("         #break\n")
+        file.write("         #case (76) //L  linear_spline\n")
+        file.write("            linear_spline\n")
+        file.write("         #break\n")
+        file.write("         #case (78) //N  linear_spline\n")
+        file.write("            natural_spline\n")
+        file.write("         #break\n")
+        file.write("         #case (81) //Q  Quadratic_spline\n")
+        file.write("            quadratic_spline\n")
+        file.write("         #break\n")
+        file.write("      #end\n")
+        file.write("      #local Add=1/((Ds-2)-1);\n")
+        file.write("      #local J=0-Add;\n")
+        file.write("      #local I=0;\n")
+        file.write("      #while (I<Ds)\n")
+        file.write("         J\n")
+        file.write("         Arr[I]\n")
+        file.write("         #local I=I+1;\n")
+        file.write("         #local J=J+Add;\n")
+        file.write("      #end\n")
+        file.write("   }\n")
+        file.write("#end\n")
+
+        file.write("#macro BuildWriteMesh2(VecArr, NormArr, UVArr, U, V, FileName)\n")
+        # suppressed some file checking from original macro because no more separate files
+        file.write(" #local Write=0;\n")
+        file.write(' #debug concat("\\n\\n Building mesh2: \\n   - vertex_vectors\\n")\n')
+        file.write("  #local NumVertices=dimension_size(VecArr,1);\n")
+        file.write("  #switch (Write)\n")
+        file.write("     #case(1)\n")
+        file.write("        #write(\n")
+        file.write("           MeshFile,\n")
+        file.write('           "  vertex_vectors {\\n",\n')
+        file.write('           "    ", str(NumVertices,0,0),"\\n    "\n')
+        file.write("        )\n")
+        file.write("     #break\n")
+        file.write("     #case(2)\n")
+        file.write("        #write(\n")
+        file.write("           MeshFile,\n")
+        file.write('           "# Vertices: ",str(NumVertices,0,0),"\\n"\n')
+        file.write("        )\n")
+        file.write("     #break\n")
+        file.write("     #case(3)\n")
+        file.write("        #write(\n")
+        file.write("           MeshFile,\n")
+        file.write('           str(2*NumVertices,0,0),",\\n"\n')
+        file.write("        )\n")
+        file.write("     #break\n")
+        file.write("     #case(4)\n")
+        file.write("        #write(\n")
+        file.write("           MeshFile,\n")
+        file.write('           "#declare VertexVectors= array[",str(NumVertices,0,0),"] {\\n  "\n')
+        file.write("        )\n")
+        file.write("     #break\n")
+        file.write("  #end\n")
+        file.write("  mesh2 {\n")
+        file.write("     vertex_vectors {\n")
+        file.write("        NumVertices\n")
+        file.write("        #local I=0;\n")
+        file.write("        #while (I<NumVertices)\n")
+        file.write("           VecArr[I]\n")
+        file.write("           #switch(Write)\n")
+        file.write("              #case(1)\n")
+        file.write("                 #write(MeshFile, VecArr[I])\n")
+        file.write("              #break\n")
+        file.write("              #case(2)\n")
+        file.write("                 #write(\n")
+        file.write("                    MeshFile,\n")
+        file.write(
+            '                    "v ", VecArr[I].x," ", VecArr[I].y," ", VecArr[I].z,"\\n"\n'
+        )
+        file.write("                 )\n")
+        file.write("              #break\n")
+        file.write("              #case(3)\n")
+        file.write("                 #write(\n")
+        file.write("                    MeshFile,\n")
+        file.write('                    VecArr[I].x,",", VecArr[I].y,",", VecArr[I].z,",\\n"\n')
+        file.write("                 )\n")
+        file.write("              #break\n")
+        file.write("              #case(4)\n")
+        file.write("                 #write(MeshFile, VecArr[I])\n")
+        file.write("              #break\n")
+        file.write("           #end\n")
+        file.write("           #local I=I+1;\n")
+        file.write("           #if(Write=1 | Write=4)\n")
+        file.write("              #if(mod(I,3)=0)\n")
+        file.write('                 #write(MeshFile,"\\n    ")\n')
+        file.write("              #end\n")
+        file.write("           #end \n")
+        file.write("        #end\n")
+        file.write("        #switch(Write)\n")
+        file.write("           #case(1)\n")
+        file.write('              #write(MeshFile,"\\n  }\\n")\n')
+        file.write("           #break\n")
+        file.write("           #case(2)\n")
+        file.write('              #write(MeshFile,"\\n")\n')
+        file.write("           #break\n")
+        file.write("           #case(3)\n")
+        file.write("              // do nothing\n")
+        file.write("           #break\n")
+        file.write("           #case(4) \n")
+        file.write('              #write(MeshFile,"\\n}\\n")\n')
+        file.write("           #break\n")
+        file.write("        #end\n")
+        file.write("     }\n")
+
+        file.write('     #debug concat("   - normal_vectors\\n")    \n')
+        file.write("     #local NumVertices=dimension_size(NormArr,1);\n")
+        file.write("     #switch(Write)\n")
+        file.write("        #case(1)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile,\n")
+        file.write('              "  normal_vectors {\\n",\n')
+        file.write('              "    ", str(NumVertices,0,0),"\\n    "\n')
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("        #case(2)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile,\n")
+        file.write('              "# Normals: ",str(NumVertices,0,0),"\\n"\n')
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("        #case(3)\n")
+        file.write("           // do nothing\n")
+        file.write("        #break\n")
+        file.write("        #case(4)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile,\n")
+        file.write(
+            '              "#declare NormalVectors= array[",str(NumVertices,0,0),"] {\\n  "\n'
+        )
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("     #end\n")
+        file.write("     normal_vectors {\n")
+        file.write("        NumVertices\n")
+        file.write("        #local I=0;\n")
+        file.write("        #while (I<NumVertices)\n")
+        file.write("           NormArr[I]\n")
+        file.write("           #switch(Write)\n")
+        file.write("              #case(1)\n")
+        file.write("                 #write(MeshFile NormArr[I])\n")
+        file.write("              #break\n")
+        file.write("              #case(2)\n")
+        file.write("                 #write(\n")
+        file.write("                    MeshFile,\n")
+        file.write(
+            '                    "vn ", NormArr[I].x," ", NormArr[I].y," ", NormArr[I].z,"\\n"\n'
+        )
+        file.write("                 )\n")
+        file.write("              #break\n")
+        file.write("              #case(3)\n")
+        file.write("                 #write(\n")
+        file.write("                    MeshFile,\n")
+        file.write('                    NormArr[I].x,",", NormArr[I].y,",", NormArr[I].z,",\\n"\n')
+        file.write("                 )\n")
+        file.write("              #break\n")
+        file.write("              #case(4)\n")
+        file.write("                 #write(MeshFile NormArr[I])\n")
+        file.write("              #break\n")
+        file.write("           #end\n")
+        file.write("           #local I=I+1;\n")
+        file.write("           #if(Write=1 | Write=4) \n")
+        file.write("              #if(mod(I,3)=0)\n")
+        file.write('                 #write(MeshFile,"\\n    ")\n')
+        file.write("              #end\n")
+        file.write("           #end\n")
+        file.write("        #end\n")
+        file.write("        #switch(Write)\n")
+        file.write("           #case(1)\n")
+        file.write('              #write(MeshFile,"\\n  }\\n")\n')
+        file.write("           #break\n")
+        file.write("           #case(2)\n")
+        file.write('              #write(MeshFile,"\\n")\n')
+        file.write("           #break\n")
+        file.write("           #case(3)\n")
+        file.write("              //do nothing\n")
+        file.write("           #break\n")
+        file.write("           #case(4)\n")
+        file.write('              #write(MeshFile,"\\n}\\n")\n')
+        file.write("           #break\n")
+        file.write("        #end\n")
+        file.write("     }\n")
+
+        file.write('     #debug concat("   - uv_vectors\\n")   \n')
+        file.write("     #local NumVertices=dimension_size(UVArr,1);\n")
+        file.write("     #switch(Write)\n")
+        file.write("        #case(1)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile, \n")
+        file.write('              "  uv_vectors {\\n",\n')
+        file.write('              "    ", str(NumVertices,0,0),"\\n    "\n')
+        file.write("           )\n")
+        file.write("         #break\n")
+        file.write("         #case(2)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile,\n")
+        file.write('              "# UV-vectors: ",str(NumVertices,0,0),"\\n"\n')
+        file.write("           )\n")
+        file.write("         #break\n")
+        file.write("         #case(3)\n")
+        file.write("           // do nothing, *.pcm does not support uv-vectors\n")
+        file.write("         #break\n")
+        file.write("         #case(4)\n")
+        file.write("            #write(\n")
+        file.write("               MeshFile,\n")
+        file.write('               "#declare UVVectors= array[",str(NumVertices,0,0),"] {\\n  "\n')
+        file.write("            )\n")
+        file.write("         #break\n")
+        file.write("     #end\n")
+        file.write("     uv_vectors {\n")
+        file.write("        NumVertices\n")
+        file.write("        #local I=0;\n")
+        file.write("        #while (I<NumVertices)\n")
+        file.write("           UVArr[I]\n")
+        file.write("           #switch(Write)\n")
+        file.write("              #case(1)\n")
+        file.write("                 #write(MeshFile UVArr[I])\n")
+        file.write("              #break\n")
+        file.write("              #case(2)\n")
+        file.write("                 #write(\n")
+        file.write("                    MeshFile,\n")
+        file.write('                    "vt ", UVArr[I].u," ", UVArr[I].v,"\\n"\n')
+        file.write("                 )\n")
+        file.write("              #break\n")
+        file.write("              #case(3)\n")
+        file.write("                 //do nothing\n")
+        file.write("              #break\n")
+        file.write("              #case(4)\n")
+        file.write("                 #write(MeshFile UVArr[I])\n")
+        file.write("              #break\n")
+        file.write("           #end\n")
+        file.write("           #local I=I+1; \n")
+        file.write("           #if(Write=1 | Write=4)\n")
+        file.write("              #if(mod(I,3)=0)\n")
+        file.write('                 #write(MeshFile,"\\n    ")\n')
+        file.write("              #end \n")
+        file.write("           #end\n")
+        file.write("        #end \n")
+        file.write("        #switch(Write)\n")
+        file.write("           #case(1)\n")
+        file.write('              #write(MeshFile,"\\n  }\\n")\n')
+        file.write("           #break\n")
+        file.write("           #case(2)\n")
+        file.write('              #write(MeshFile,"\\n")\n')
+        file.write("           #break\n")
+        file.write("           #case(3)\n")
+        file.write("              //do nothing\n")
+        file.write("           #break\n")
+        file.write("           #case(4)\n")
+        file.write('              #write(MeshFile,"\\n}\\n")\n')
+        file.write("           #break\n")
+        file.write("        #end\n")
+        file.write("     }\n")
+        file.write("\n")
+        file.write('     #debug concat("   - face_indices\\n")   \n')
+        file.write("     #declare NumFaces=U*V*2;\n")
+        file.write("     #switch(Write)\n")
+        file.write("        #case(1)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile,\n")
+        file.write('              "  face_indices {\\n"\n')
+        file.write('              "    ", str(NumFaces,0,0),"\\n    "\n')
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("        #case(2)\n")
+        file.write("           #write (\n")
+        file.write("              MeshFile,\n")
+        file.write('              "# faces: ",str(NumFaces,0,0),"\\n"\n')
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("        #case(3)\n")
+        file.write("           #write (\n")
+        file.write("              MeshFile,\n")
+        file.write('              "0,",str(NumFaces,0,0),",\\n"\n')
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("        #case(4)\n")
+        file.write("           #write(\n")
+        file.write("              MeshFile,\n")
+        file.write('              "#declare FaceIndices= array[",str(NumFaces,0,0),"] {\\n  "\n')
+        file.write("           )\n")
+        file.write("        #break\n")
+        file.write("     #end\n")
+        file.write("     face_indices {\n")
+        file.write("        NumFaces\n")
+        file.write("        #local I=0;\n")
+        file.write("        #local H=0;\n")
+        file.write("        #local NumVertices=dimension_size(VecArr,1);\n")
+        file.write("        #while (I<V)\n")
+        file.write("           #local J=0;\n")
+        file.write("           #while (J<U)\n")
+        file.write("              #local Ind=(I*U)+I+J;\n")
+        file.write("              <Ind, Ind+1, Ind+U+2>, <Ind, Ind+U+1, Ind+U+2>\n")
+        file.write("              #switch(Write)\n")
+        file.write("                 #case(1)\n")
+        file.write("                    #write(\n")
+        file.write("                       MeshFile,\n")
+        file.write("                       <Ind, Ind+1, Ind+U+2>, <Ind, Ind+U+1, Ind+U+2>\n")
+        file.write("                    )\n")
+        file.write("                 #break\n")
+        file.write("                 #case(2)\n")
+        file.write("                    #write(\n")
+        file.write("                       MeshFile,\n")
+        file.write(
+            '                       "f ",Ind+1,"/",Ind+1,"/",Ind+1," ",Ind+1+1,"/",Ind+1+1,"/",Ind+1+1," ",Ind+U+2+1,"/",Ind+U+2+1,"/",Ind+U+2+1,"\\n",\n'
+        )
+        file.write(
+            '                       "f ",Ind+U+1+1,"/",Ind+U+1+1,"/",Ind+U+1+1," ",Ind+1,"/",Ind+1,"/",Ind+1," ",Ind+U+2+1,"/",Ind+U+2+1,"/",Ind+U+2+1,"\\n"\n'
+        )
+        file.write("                    )\n")
+        file.write("                 #break\n")
+        file.write("                 #case(3)\n")
+        file.write("                    #write(\n")
+        file.write("                       MeshFile,\n")
+        file.write(
+            '                       Ind,",",Ind+NumVertices,",",Ind+1,",",Ind+1+NumVertices,",",Ind+U+2,",",Ind+U+2+NumVertices,",\\n"\n'
+        )
+        file.write(
+            '                       Ind+U+1,",",Ind+U+1+NumVertices,",",Ind,",",Ind+NumVertices,",",Ind+U+2,",",Ind+U+2+NumVertices,",\\n"\n'
+        )
+        file.write("                    )\n")
+        file.write("                 #break\n")
+        file.write("                 #case(4)\n")
+        file.write("                    #write(\n")
+        file.write("                       MeshFile,\n")
+        file.write("                       <Ind, Ind+1, Ind+U+2>, <Ind, Ind+U+1, Ind+U+2>\n")
+        file.write("                    )\n")
+        file.write("                 #break\n")
+        file.write("              #end\n")
+        file.write("              #local J=J+1;\n")
+        file.write("              #local H=H+1;\n")
+        file.write("              #if(Write=1 | Write=4)\n")
+        file.write("                 #if(mod(H,3)=0)\n")
+        file.write('                    #write(MeshFile,"\\n    ")\n')
+        file.write("                 #end \n")
+        file.write("              #end\n")
+        file.write("           #end\n")
+        file.write("           #local I=I+1;\n")
+        file.write("        #end\n")
+        file.write("     }\n")
+        file.write("     #switch(Write)\n")
+        file.write("        #case(1)\n")
+        file.write('           #write(MeshFile, "\\n  }\\n}")\n')
+        file.write("           #fclose MeshFile\n")
+        file.write('           #debug concat(" Done writing\\n")\n')
+        file.write("        #break\n")
+        file.write("        #case(2)\n")
+        file.write("           #fclose MeshFile\n")
+        file.write('           #debug concat(" Done writing\\n")\n')
+        file.write("        #break\n")
+        file.write("        #case(3)\n")
+        file.write("           #fclose MeshFile\n")
+        file.write('           #debug concat(" Done writing\\n")\n')
+        file.write("        #break\n")
+        file.write("        #case(4)\n")
+        file.write('           #write(MeshFile, "\\n}\\n}")\n')
+        file.write("           #fclose MeshFile\n")
+        file.write('           #debug concat(" Done writing\\n")\n')
+        file.write("        #break\n")
+        file.write("     #end\n")
+        file.write("  }\n")
+        file.write("#end\n")
+
+        file.write("#macro MSM(SplineArray, SplRes, Interp_type,  InterpRes, FileName)\n")
+        file.write("    #declare Build=CheckFileName(FileName);\n")
+        file.write("    #if(Build=0)\n")
+        file.write('        #debug concat("\\n Parsing mesh2 from file: ", FileName, "\\n")\n')
+        file.write("        #include FileName\n")
+        file.write("        object{Surface}\n")
+        file.write("    #else\n")
+        file.write("        #local NumVertices=(SplRes+1)*(InterpRes+1);\n")
+        file.write("        #local NumFaces=SplRes*InterpRes*2;\n")
+        file.write(
+            '        #debug concat("\\n Calculating ",str(NumVertices,0,0)," vertices for ", str(NumFaces,0,0)," triangles\\n\\n")\n'
+        )
+        file.write("        #local VecArr=array[NumVertices]\n")
+        file.write("        #local NormArr=array[NumVertices]\n")
+        file.write("        #local UVArr=array[NumVertices]\n")
+        file.write("        #local N=dimension_size(SplineArray,1);\n")
+        file.write("        #local TempSplArr0=array[N];\n")
+        file.write("        #local TempSplArr1=array[N];\n")
+        file.write("        #local TempSplArr2=array[N];\n")
+        file.write("        #local PosStep=1/SplRes;\n")
+        file.write("        #local InterpStep=1/InterpRes;\n")
+        file.write("        #local Count=0;\n")
+        file.write("        #local Pos=0;\n")
+        file.write("        #while(Pos<=1)\n")
+        file.write("            #local I=0;\n")
+        file.write("            #if (Pos=0)\n")
+        file.write("                #while (I<N)\n")
+        file.write("                    #local Spl=spline{SplineArray[I]}\n")
+        file.write("                    #local TempSplArr0[I]=<0,0,0>+Spl(Pos);\n")
+        file.write("                    #local TempSplArr1[I]=<0,0,0>+Spl(Pos+PosStep);\n")
+        file.write("                    #local TempSplArr2[I]=<0,0,0>+Spl(Pos-PosStep);\n")
+        file.write("                    #local I=I+1;\n")
+        file.write("                #end\n")
+        file.write("                #local S0=BuildSpline(TempSplArr0, Interp_type)\n")
+        file.write("                #local S1=BuildSpline(TempSplArr1, Interp_type)\n")
+        file.write("                #local S2=BuildSpline(TempSplArr2, Interp_type)\n")
+        file.write("            #else\n")
+        file.write("                #while (I<N)\n")
+        file.write("                    #local Spl=spline{SplineArray[I]}\n")
+        file.write("                    #local TempSplArr1[I]=<0,0,0>+Spl(Pos+PosStep);\n")
+        file.write("                    #local I=I+1;\n")
+        file.write("                #end\n")
+        file.write("                #local S1=BuildSpline(TempSplArr1, Interp_type)\n")
+        file.write("            #end\n")
+        file.write("            #local J=0;\n")
+        file.write("            #while (J<=1)\n")
+        file.write("                #local P0=<0,0,0>+S0(J);\n")
+        file.write("                #local P1=<0,0,0>+S1(J);\n")
+        file.write("                #local P2=<0,0,0>+S2(J);\n")
+        file.write("                #local P3=<0,0,0>+S0(J+InterpStep);\n")
+        file.write("                #local P4=<0,0,0>+S0(J-InterpStep);\n")
+        file.write("                #local B1=P4-P0;\n")
+        file.write("                #local B2=P2-P0;\n")
+        file.write("                #local B3=P3-P0;\n")
+        file.write("                #local B4=P1-P0;\n")
+        file.write("                #local N1=vcross(B1,B2);\n")
+        file.write("                #local N2=vcross(B2,B3);\n")
+        file.write("                #local N3=vcross(B3,B4);\n")
+        file.write("                #local N4=vcross(B4,B1);\n")
+        file.write("                #local Norm=vnormalize((N1+N2+N3+N4));\n")
+        file.write("                #local VecArr[Count]=P0;\n")
+        file.write("                #local NormArr[Count]=Norm;\n")
+        file.write("                #local UVArr[Count]=<J,Pos>;\n")
+        file.write("                #local J=J+InterpStep;\n")
+        file.write("                #local Count=Count+1;\n")
+        file.write("            #end\n")
+        file.write("            #local S2=spline{S0}\n")
+        file.write("            #local S0=spline{S1}\n")
+        file.write(
+            '            #debug concat("\\r Done ", str(Count,0,0)," vertices : ", str(100*Count/NumVertices,0,2)," %")\n'
+        )
+        file.write("            #local Pos=Pos+PosStep;\n")
+        file.write("        #end\n")
+        file.write('        BuildWriteMesh2(VecArr, NormArr, UVArr, InterpRes, SplRes, "")\n')
+        file.write("    #end\n")
+        file.write("#end\n\n")
+
+        file.write("#macro Coons(Spl1, Spl2, Spl3, Spl4, Iter_U, Iter_V, FileName)\n")
+        file.write("   #declare Build=CheckFileName(FileName);\n")
+        file.write("   #if(Build=0)\n")
+        file.write('      #debug concat("\\n Parsing mesh2 from file: ", FileName, "\\n")\n')
+        file.write("      #include FileName\n")
+        file.write("      object{Surface}\n")
+        file.write("   #else\n")
+        file.write("      #local NumVertices=(Iter_U+1)*(Iter_V+1);\n")
+        file.write("      #local NumFaces=Iter_U*Iter_V*2;\n")
+        file.write(
+            '      #debug concat("\\n Calculating ", str(NumVertices,0,0), " vertices for ",str(NumFaces,0,0), " triangles\\n\\n")\n'
+        )
+        file.write("      #declare VecArr=array[NumVertices]   \n")
+        file.write("      #declare NormArr=array[NumVertices]   \n")
+        file.write("      #local UVArr=array[NumVertices]      \n")
+        file.write("      #local Spl1_0=Spl1(0);\n")
+        file.write("      #local Spl2_0=Spl2(0);\n")
+        file.write("      #local Spl3_0=Spl3(0);\n")
+        file.write("      #local Spl4_0=Spl4(0);\n")
+        file.write("      #local UStep=1/Iter_U;\n")
+        file.write("      #local VStep=1/Iter_V;\n")
+        file.write("      #local Count=0;\n")
+        file.write("      #local I=0;\n")
+        file.write("      #while (I<=1)\n")
+        file.write("         #local Im=1-I;\n")
+        file.write("         #local J=0;\n")
+        file.write("         #while (J<=1)\n")
+        file.write("            #local Jm=1-J;\n")
+        file.write(
+            "            #local C0=Im*Jm*(Spl1_0)+Im*J*(Spl2_0)+I*J*(Spl3_0)+I*Jm*(Spl4_0);\n"
+        )
+        file.write("            #local P0=LInterpolate(I, Spl1(J), Spl3(Jm)) + \n")
+        file.write("               LInterpolate(Jm, Spl2(I), Spl4(Im))-C0;\n")
+        file.write("            #declare VecArr[Count]=P0;\n")
+        file.write("            #local UVArr[Count]=<J,I>;\n")
+        file.write("            #local J=J+UStep;\n")
+        file.write("            #local Count=Count+1;\n")
+        file.write("         #end\n")
+        file.write("         #debug concat(\n")
+        file.write('            "\r Done ", str(Count,0,0)," vertices :         ",\n')
+        file.write('            str(100*Count/NumVertices,0,2)," %"\n')
+        file.write("         )\n")
+        file.write("         #local I=I+VStep;\n")
+        file.write("      #end\n")
+        file.write('      #debug "\r Normals                                  "\n')
+        file.write("      #local Count=0;\n")
+        file.write("      #local I=0;\n")
+        file.write("      #while (I<=Iter_V)\n")
+        file.write("         #local J=0;\n")
+        file.write("         #while (J<=Iter_U)\n")
+        file.write("            #local Ind=(I*Iter_U)+I+J;\n")
+        file.write("            #local P0=VecArr[Ind];\n")
+        file.write("            #if(J=0)\n")
+        file.write("               #local P1=P0+(P0-VecArr[Ind+1]);\n")
+        file.write("            #else\n")
+        file.write("               #local P1=VecArr[Ind-1];\n")
+        file.write("            #end\n")
+        file.write("            #if (J=Iter_U)\n")
+        file.write("               #local P2=P0+(P0-VecArr[Ind-1]);\n")
+        file.write("            #else\n")
+        file.write("               #local P2=VecArr[Ind+1];\n")
+        file.write("            #end\n")
+        file.write("            #if (I=0)\n")
+        file.write("               #local P3=P0+(P0-VecArr[Ind+Iter_U+1]);\n")
+        file.write("            #else\n")
+        file.write("               #local P3=VecArr[Ind-Iter_U-1];\n")
+        file.write("            #end\n")
+        file.write("            #if (I=Iter_V)\n")
+        file.write("               #local P4=P0+(P0-VecArr[Ind-Iter_U-1]);\n")
+        file.write("            #else\n")
+        file.write("               #local P4=VecArr[Ind+Iter_U+1];\n")
+        file.write("            #end\n")
+        file.write("            #local B1=P4-P0;\n")
+        file.write("            #local B2=P2-P0;\n")
+        file.write("            #local B3=P3-P0;\n")
+        file.write("            #local B4=P1-P0;\n")
+        file.write("            #local N1=vcross(B1,B2);\n")
+        file.write("            #local N2=vcross(B2,B3);\n")
+        file.write("            #local N3=vcross(B3,B4);\n")
+        file.write("            #local N4=vcross(B4,B1);\n")
+        file.write("            #local Norm=vnormalize((N1+N2+N3+N4));\n")
+        file.write("            #declare NormArr[Count]=Norm;\n")
+        file.write("            #local J=J+1;\n")
+        file.write("            #local Count=Count+1;\n")
+        file.write("         #end\n")
+        file.write(
+            '         #debug concat("\r Done ", str(Count,0,0)," normals : ",str(100*Count/NumVertices,0,2), " %")\n'
+        )
+        file.write("         #local I=I+1;\n")
+        file.write("      #end\n")
+        file.write("      BuildWriteMesh2(VecArr, NormArr, UVArr, Iter_U, Iter_V, FileName)\n")
+        file.write("   #end\n")
+        file.write("#end\n\n")
+    # Empty curves
+    if len(ob.data.splines) == 0:
+        tab_write(file, "\n//dummy sphere to represent empty curve location\n")
+        tab_write(file, "#declare %s =\n" % dataname)
+        tab_write(
+            file,
+            "sphere {<%.6g, %.6g, %.6g>,0 pigment{rgbt 1} "
+            "no_image no_reflection no_radiosity "
+            "photons{pass_through collect off} hollow}\n\n"
+            % (ob.location.x, ob.location.y, ob.location.z),
+        )  # ob.name > povdataname)
+    # And non empty curves
+    else:
+        if not bezier_sweep:
+            tab_write(file, "#declare %s =\n" % dataname)
+        if ob.pov.curveshape == "sphere_sweep" and not bezier_sweep:
+            tab_write(file, "union {\n")
+            for spl in ob.data.splines:
+                if spl.type != "BEZIER":
+                    spl_type = "linear"
+                    if spl.type == "NURBS":
+                        spl_type = "cubic"
+                    points = spl.points
+                    num_points = len(points)
+                    if spl.use_cyclic_u:
+                        num_points += 3
+
+                    tab_write(file, "sphere_sweep { %s_spline %s,\n" % (spl_type, num_points))
+                    if spl.use_cyclic_u:
+                        pt1 = points[len(points) - 1]
+                        wpt1 = pt1.co
+                        tab_write(
+                            file,
+                            "<%.4g,%.4g,%.4g>,%.4g\n"
+                            % (
+                                wpt1[0],
+                                wpt1[1],
+                                wpt1[2],
+                                pt1.radius * ob.data.bevel_depth,
+                            ),
+                        )
+                    for pt in points:
+                        wpt = pt.co
+                        tab_write(
+                            file,
+                            "<%.4g,%.4g,%.4g>,%.4g\n"
+                            % (wpt[0], wpt[1], wpt[2], pt.radius * ob.data.bevel_depth),
+                        )
+                    if spl.use_cyclic_u:
+                        for i in range(0, 2):
+                            end_pt = points[i]
+                            wpt = end_pt.co
+                            tab_write(
+                                file,
+                                "<%.4g,%.4g,%.4g>,%.4g\n"
+                                % (
+                                    wpt[0],
+                                    wpt[1],
+                                    wpt[2],
+                                    end_pt.radius * ob.data.bevel_depth,
+                                ),
+                            )
+
+                tab_write(file, "}\n")
+        # below not used yet?
+        if ob.pov.curveshape == "sor":
+            for spl in ob.data.splines:
+                if spl.type in ("POLY", "NURBS"):
+                    points = spl.points
+                    num_points = len(points)
+                    tab_write(file, "sor { %s,\n" % num_points)
+                    for pt in points:
+                        wpt = pt.co
+                        tab_write(file, "<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
+                else:
+                    tab_write(file, "box { 0,0\n")
+        if ob.pov.curveshape in ("lathe", "prism"):
+            spl = ob.data.splines[0]
+            if spl.type == "BEZIER":
+                points = spl.bezier_points
+                len_cur = len(points) - 1
+                len_pts = len_cur * 4
+                ifprism = ""
+                if ob.pov.curveshape == "prism":
+                    height = ob.data.extrude
+                    ifprism = "-%s, %s," % (height, height)
+                    len_cur += 1
+                    len_pts += 4
+                tab_write(
+                    file, "%s { bezier_spline %s %s,\n" % (ob.pov.curveshape, ifprism, len_pts)
+                )
+                for i in range(0, len_cur):
+                    p1 = points[i].co
+                    pR = points[i].handle_right
+                    end = i + 1
+                    if i == len_cur - 1 and ob.pov.curveshape == "prism":
+                        end = 0
+                    pL = points[end].handle_left
+                    p2 = points[end].co
+                    line = "<%.4g,%.4g>" % (p1[0], p1[1])
+                    line += "<%.4g,%.4g>" % (pR[0], pR[1])
+                    line += "<%.4g,%.4g>" % (pL[0], pL[1])
+                    line += "<%.4g,%.4g>" % (p2[0], p2[1])
+                    tab_write(file, "%s\n" % line)
+            else:
+                points = spl.points
+                len_cur = len(points)
+                len_pts = len_cur
+                ifprism = ""
+                if ob.pov.curveshape == "prism":
+                    height = ob.data.extrude
+                    ifprism = "-%s, %s," % (height, height)
+                    len_pts += 3
+                spl_type = "quadratic"
+                if spl.type == "POLY":
+                    spl_type = "linear"
+                tab_write(
+                    file,
+                    "%s { %s_spline %s %s,\n" % (ob.pov.curveshape, spl_type, ifprism, len_pts),
+                )
+                if ob.pov.curveshape == "prism":
+                    pt = points[len(points) - 1]
+                    wpt = pt.co
+                    tab_write(file, "<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
+                for pt in points:
+                    wpt = pt.co
+                    tab_write(file, "<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
+                if ob.pov.curveshape == "prism":
+                    for i in range(2):
+                        pt = points[i]
+                        wpt = pt.co
+                        tab_write(file, "<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
+        if bezier_sweep:
+            for p, spl in enumerate(ob.data.splines, start=1):
+                br = []
+                depth = ob.data.bevel_depth
+                points = spl.bezier_points
+                len_cur = len(points) - 1
+                num_points = len_cur * 4
+                if spl.use_cyclic_u:
+                    len_cur += 1
+                    num_points += 4
+                tab_write(file, "#declare %s_points_%s = array[%s]{\n" % (dataname, p, num_points))
+                for i in range(len_cur):
+                    p1 = points[i].co
+                    pR = points[i].handle_right
+                    end = i + 1
+                    if spl.use_cyclic_u and i == (len_cur - 1):
+                        end = 0
+                    pL = points[end].handle_left
+                    p2 = points[end].co
+                    r3 = points[end].radius * depth
+                    r0 = points[i].radius * depth
+                    r1 = 2 / 3 * r0 + 1 / 3 * r3
+                    r2 = 1 / 3 * r0 + 2 / 3 * r3
+                    br.append((r0, r1, r2, r3))
+                    line = "<%.4g,%.4g,%.4f>" % (p1[0], p1[1], p1[2])
+                    line += "<%.4g,%.4g,%.4f>" % (pR[0], pR[1], pR[2])
+                    line += "<%.4g,%.4g,%.4f>" % (pL[0], pL[1], pL[2])
+                    line += "<%.4g,%.4g,%.4f>" % (p2[0], p2[1], p2[2])
+                    tab_write(file, "%s\n" % line)
+                tab_write(file, "}\n")
+                tab_write(file, "#declare %s_radii_%s = array[%s]{\n" % (dataname, p, len(br) * 4))
+                for rad_tuple in br:
+                    tab_write(
+                        file,
+                        "%.4f,%.4f,%.4f,%.4f\n"
+                        % (rad_tuple[0], rad_tuple[1], rad_tuple[2], rad_tuple[3]),
+                    )
+                tab_write(file, "}\n")
+            if len(ob.data.splines) == 1:
+                p = 1
+                tab_write(file, "#declare %s = object{\n" % dataname)
+                tab_write(
+                    file,
+                    "    Shape_Bezierpoints_Sphere_Sweep(yes,%s, %s_points_%s, %s_radii_%s) \n"
+                    % (ob.data.resolution_u, dataname, p, dataname, p),
+                )
+            else:
+                tab_write(file, "#declare %s = union{\n" % dataname)
+                for p, spl in enumerate(ob.data.splines, start=1):
+                    tab_write(
+                        file,
+                        "    object{Shape_Bezierpoints_Sphere_Sweep(yes,%s, %s_points_%s, %s_radii_%s)} \n"
+                        % (ob.data.resolution_u, dataname, p, dataname, p),
+                    )
+                # tab_write(file, '#include "bezier_spheresweep.inc"\n') #now inlined
+            # tab_write(file, '#declare %s = object{Shape_Bezierpoints_Sphere_Sweep(yes,%s, %s_bezier_points, %.4f) \n'%(dataname,ob.data.resolution_u,dataname,ob.data.bevel_depth))
+        if ob.pov.curveshape == "loft":
+            tab_write(
+                file, 'object {MSM(%s,%s,"c",%s,"")\n' % (dataname, ob.pov.res_u, ob.pov.res_v)
+            )
+        if ob.pov.curveshape == "birail":
+            splines = "%s1,%s2,%s3,%s4" % (dataname, dataname, dataname, dataname)
+            tab_write(
+                file, 'object {Coons(%s, %s, %s, "")\n' % (splines, ob.pov.res_u, ob.pov.res_v)
+            )
+        # pov_mat_name = "Default_texture" # XXX! Unused, check instantiation
+        if ob.active_material:
+            # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(ob.active_material.name))
+            try:
+                material = ob.active_material
+                write_object_material_interior(file, material, ob, tab_write)
+            except IndexError:
+                print(ob.data)
+        # tab_write(file, "texture {%s}\n"%pov_mat_name)
+        if ob.pov.curveshape == "prism":
+            tab_write(file, "rotate <90,0,0>\n")
+            tab_write(file, "scale y*-1\n")
+        tab_write(file, "}\n")
diff --git a/render_povray/object_gui.py b/render_povray/model_gui.py
old mode 100755
new mode 100644
similarity index 74%
rename from render_povray/object_gui.py
rename to render_povray/model_gui.py
index 2033a0d46..2ffebf20e
--- a/render_povray/object_gui.py
+++ b/render_povray/model_gui.py
@@ -7,12 +7,7 @@ import bpy
 
 import os
 
-from bpy.utils import (
-    register_class,
-    unregister_class,
-    register_tool,
-    unregister_tool
-)
+from bpy.utils import register_class, unregister_class, register_tool, unregister_tool
 from bpy.types import (
     # Operator,
     Menu,
@@ -26,15 +21,15 @@ from bl_ui import properties_data_modifier
 for member in dir(properties_data_modifier):
     subclass = getattr(properties_data_modifier, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_data_modifier
 
 
 from bl_ui import properties_data_mesh
 
 # These panels are kept
-properties_data_mesh.DATA_PT_custom_props_mesh.COMPAT_ENGINES.add('POVRAY_RENDER')
-properties_data_mesh.DATA_PT_context_mesh.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_data_mesh.DATA_PT_custom_props_mesh.COMPAT_ENGINES.add("POVRAY_RENDER")
+properties_data_mesh.DATA_PT_context_mesh.COMPAT_ENGINES.add("POVRAY_RENDER")
 
 # make some native panels contextual to some object variable
 # by recreating custom panels inheriting their properties
@@ -47,10 +42,10 @@ class ModifierButtonsPanel:
     """Use this class to define buttons from the modifier tab of
     properties window."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "modifier"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -63,10 +58,10 @@ class ObjectButtonsPanel:
     """Use this class to define buttons from the object tab of
     properties window."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "object"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -79,22 +74,22 @@ class PovDataButtonsPanel(properties_data_mesh.MeshButtonsPanel):
     """Use this class to define buttons from the edit data tab of
     properties window."""
 
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     POV_OBJECT_TYPES = {
-        'PLANE',
-        'BOX',
-        'SPHERE',
-        'CYLINDER',
-        'CONE',
-        'TORUS',
-        'BLOB',
-        'ISOSURFACE_NODE',
-        'ISOSURFACE_VIEW',
-        'SUPERELLIPSOID',
-        'SUPERTORUS',
-        'HEIGHT_FIELD',
-        'PARAMETRIC',
-        'POLYCIRCLE',
+        "PLANE",
+        "BOX",
+        "SPHERE",
+        "CYLINDER",
+        "CONE",
+        "TORUS",
+        "BLOB",
+        "ISOSURFACE_NODE",
+        "ISOSURFACE_VIEW",
+        "SUPERELLIPSOID",
+        "SUPERTORUS",
+        "HEIGHT_FIELD",
+        "PARAMETRIC",
+        "POLYCIRCLE",
     }
 
     @classmethod
@@ -176,7 +171,7 @@ class MODIFIERS_PT_POV_modifiers(ModifierButtonsPanel, Panel):
         once_csg = 0
         for mod in ob.modifiers:
             if once_csg == 0 and mod:
-                if mod.type == 'BOOLEAN':
+                if mod.type == "BOOLEAN":
                     col.prop(ob.pov, "boolean_mod")
                     once_csg = 1
 
@@ -220,7 +215,7 @@ class OBJECT_PT_POV_obj_parameters(ObjectButtonsPanel, Panel):
         col.prop(obj.pov, "hollow")
         col.prop(obj.pov, "double_illuminate")
 
-        if obj.type == 'META' or obj.pov.curveshape == 'lathe':
+        if obj.type == "META" or obj.pov.curveshape == "lathe":
             # if obj.pov.curveshape == 'sor'
             col.prop(obj.pov, "sturm")
         col.prop(obj.pov, "no_shadow")
@@ -242,14 +237,14 @@ class OBJECT_PT_POV_obj_sphere(PovDataButtonsPanel, Panel):
     """Use this class to define pov sphere primitive parameters buttons."""
 
     bl_label = "POV Sphere"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'SPHERE' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "SPHERE" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -258,16 +253,16 @@ class OBJECT_PT_POV_obj_sphere(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'SPHERE':
+        if obj.pov.object_as == "SPHERE":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Sphere radius: " + str(obj.pov.sphere_radius))
 
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -282,14 +277,14 @@ class OBJECT_PT_POV_obj_cylinder(PovDataButtonsPanel, Panel):
     """Use this class to define pov cylinder primitive parameters buttons."""
 
     bl_label = "POV Cylinder"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'CYLINDER' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "CYLINDER" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -298,21 +293,17 @@ class OBJECT_PT_POV_obj_cylinder(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'CYLINDER':
+        if obj.pov.object_as == "CYLINDER":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters",
-                    text="Exported parameters below",
-                    icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Cylinder radius: " + str(obj.pov.cylinder_radius))
                 col.label(text="Cylinder cap location: " + str(obj.pov.cylinder_location_cap))
 
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters",
-                    text="Edit exported parameters",
-                    icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -328,14 +319,14 @@ class OBJECT_PT_POV_obj_cone(PovDataButtonsPanel, Panel):
     """Use this class to define pov cone primitive parameters buttons."""
 
     bl_label = "POV Cone"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'CONE' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "CONE" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -344,10 +335,10 @@ class OBJECT_PT_POV_obj_cone(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'CONE':
+        if obj.pov.object_as == "CONE":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Cone base radius: " + str(obj.pov.cone_base_radius))
                 col.label(text="Cone cap radius: " + str(obj.pov.cone_cap_radius))
@@ -355,7 +346,7 @@ class OBJECT_PT_POV_obj_cone(PovDataButtonsPanel, Panel):
                 col.label(text="Cone height: " + str(obj.pov.cone_height))
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -373,14 +364,14 @@ class OBJECT_PT_POV_obj_superellipsoid(PovDataButtonsPanel, Panel):
     """Use this class to define pov superellipsoid primitive parameters buttons."""
 
     bl_label = "POV Superquadric ellipsoid"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'SUPERELLIPSOID' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "SUPERELLIPSOID" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -389,10 +380,10 @@ class OBJECT_PT_POV_obj_superellipsoid(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'SUPERELLIPSOID':
+        if obj.pov.object_as == "SUPERELLIPSOID":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Radial segmentation: " + str(obj.pov.se_u))
                 col.label(text="Lateral segmentation: " + str(obj.pov.se_v))
@@ -401,7 +392,7 @@ class OBJECT_PT_POV_obj_superellipsoid(PovDataButtonsPanel, Panel):
                 col.label(text="Fill up and down: " + str(obj.pov.se_edit))
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -420,14 +411,14 @@ class OBJECT_PT_POV_obj_torus(PovDataButtonsPanel, Panel):
     """Use this class to define pov torus primitive parameters buttons."""
 
     bl_label = "POV Torus"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'TORUS' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "TORUS" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -436,10 +427,10 @@ class OBJECT_PT_POV_obj_torus(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'TORUS':
+        if obj.pov.object_as == "TORUS":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Torus major radius: " + str(obj.pov.torus_major_radius))
                 col.label(text="Torus minor radius: " + str(obj.pov.torus_minor_radius))
@@ -447,7 +438,7 @@ class OBJECT_PT_POV_obj_torus(PovDataButtonsPanel, Panel):
                 col.label(text="Torus minor segments: " + str(obj.pov.torus_minor_segments))
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -465,14 +456,14 @@ class OBJECT_PT_POV_obj_supertorus(PovDataButtonsPanel, Panel):
     """Use this class to define pov supertorus primitive parameters buttons."""
 
     bl_label = "POV SuperTorus"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'SUPERTORUS' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "SUPERTORUS" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -481,10 +472,10 @@ class OBJECT_PT_POV_obj_supertorus(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'SUPERTORUS':
+        if obj.pov.object_as == "SUPERTORUS":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="SuperTorus major radius: " + str(obj.pov.st_major_radius))
                 col.label(text="SuperTorus minor radius: " + str(obj.pov.st_minor_radius))
@@ -500,7 +491,7 @@ class OBJECT_PT_POV_obj_supertorus(PovDataButtonsPanel, Panel):
 
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -524,14 +515,14 @@ class OBJECT_PT_POV_obj_isosurface(PovDataButtonsPanel, Panel):
     """Use this class to define pov generic isosurface primitive function user field."""
 
     bl_label = "POV Isosurface"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'ISOSURFACE_VIEW' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "ISOSURFACE_VIEW" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -540,8 +531,9 @@ class OBJECT_PT_POV_obj_isosurface(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'ISOSURFACE_VIEW':
-                col.prop(obj.pov, "isosurface_eq")
+        if obj.pov.object_as == "ISOSURFACE_VIEW":
+            col.prop(obj.pov, "isosurface_eq")
+
 
 class OBJECT_PT_POV_obj_parametric(PovDataButtonsPanel, Panel):
     """Use this class to define pov parametric surface primitive parameters buttons."""
@@ -553,7 +545,7 @@ class OBJECT_PT_POV_obj_parametric(PovDataButtonsPanel, Panel):
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'PARAMETRIC' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "PARAMETRIC" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -562,10 +554,10 @@ class OBJECT_PT_POV_obj_parametric(PovDataButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'PARAMETRIC':
+        if obj.pov.object_as == "PARAMETRIC":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Minimum U: " + str(obj.pov.u_min))
                 col.label(text="Minimum V: " + str(obj.pov.v_min))
@@ -577,7 +569,7 @@ class OBJECT_PT_POV_obj_parametric(PovDataButtonsPanel, Panel):
 
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
@@ -597,7 +589,7 @@ class OBJECT_PT_povray_replacement_text(ObjectButtonsPanel, Panel):
     """Use this class to define pov object replacement field."""
 
     bl_label = "Custom POV Code"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
@@ -624,7 +616,7 @@ def check_add_mesh_extra_objects():
 def menu_func_add(self, context):
     """Append the POV primitives submenu to blender add objects menu"""
     engine = context.scene.render.engine
-    if engine == 'POVRAY_RENDER':
+    if engine == "POVRAY_RENDER":
         self.layout.menu("VIEW_MT_POV_primitives_add", icon="PLUGIN")
 
 
@@ -633,16 +625,16 @@ class VIEW_MT_POV_primitives_add(Menu):
 
     bl_idname = "VIEW_MT_POV_primitives_add"
     bl_label = "Povray"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
-        return engine == 'POVRAY_RENDER'
+        return engine == "POVRAY_RENDER"
 
     def draw(self, context):
         layout = self.layout
-        layout.operator_context = 'INVOKE_REGION_WIN'
+        layout.operator_context = "INVOKE_REGION_WIN"
         layout.menu(VIEW_MT_POV_Basic_Shapes.bl_idname, text="Primitives", icon="GROUP")
         layout.menu(VIEW_MT_POV_import.bl_idname, text="Import", icon="IMPORT")
 
@@ -655,22 +647,22 @@ class VIEW_MT_POV_Basic_Shapes(Menu):
 
     def draw(self, context):
         layout = self.layout
-        layout.operator_context = 'INVOKE_REGION_WIN'
-        layout.operator("pov.addplane", text="Infinite Plane", icon='MESH_PLANE')
-        layout.operator("pov.addbox", text="Box", icon='MESH_CUBE')
-        layout.operator("pov.addsphere", text="Sphere", icon='SHADING_RENDERED')
+        layout.operator_context = "INVOKE_REGION_WIN"
+        layout.operator("pov.addplane", text="Infinite Plane", icon="MESH_PLANE")
+        layout.operator("pov.addbox", text="Box", icon="MESH_CUBE")
+        layout.operator("pov.addsphere", text="Sphere", icon="SHADING_RENDERED")
         layout.operator("pov.addcylinder", text="Cylinder", icon="MESH_CYLINDER")
         layout.operator("pov.addcone", text="Cone", icon="MESH_CONE")
-        layout.operator("pov.addtorus", text="Torus", icon='MESH_TORUS')
+        layout.operator("pov.addtorus", text="Torus", icon="MESH_TORUS")
         layout.separator()
         layout.operator("pov.addrainbow", text="Rainbow", icon="COLOR")
-        layout.operator("pov.addlathe", text="Lathe", icon='MOD_SCREW')
-        layout.operator("pov.addprism", text="Prism", icon='MOD_SOLIDIFY')
-        layout.operator("pov.addsuperellipsoid", text="Superquadric Ellipsoid", icon='MOD_SUBSURF')
+        layout.operator("pov.addlathe", text="Lathe", icon="MOD_SCREW")
+        layout.operator("pov.addprism", text="Prism", icon="MOD_SOLIDIFY")
+        layout.operator("pov.addsuperellipsoid", text="Superquadric Ellipsoid", icon="MOD_SUBSURF")
         layout.operator("pov.addheightfield", text="Height Field", icon="RNDCURVE")
-        layout.operator("pov.addspheresweep", text="Sphere Sweep", icon='FORCE_CURVE')
+        layout.operator("pov.addspheresweep", text="Sphere Sweep", icon="FORCE_CURVE")
         layout.separator()
-        layout.operator("pov.addblobsphere", text="Blob Sphere", icon='META_DATA')
+        layout.operator("pov.addblobsphere", text="Blob Sphere", icon="META_DATA")
         layout.separator()
         layout.label(text="Isosurfaces")
         layout.operator("pov.addisosurfacebox", text="Isosurface Box", icon="META_CUBE")
@@ -697,464 +689,377 @@ class VIEW_MT_POV_Basic_Shapes(Menu):
 
             # layout.separator()
             return
-        layout.operator("pov.addparametric", text="Parametric", icon='SCRIPTPLUGINS')
+        layout.operator("pov.addparametric", text="Parametric", icon="SCRIPTPLUGINS")
+
 
 # ------------ Tool bar button------------ #
-icon_path = (os.path.join(os.path.dirname(__file__), "icons"))
+icon_path = os.path.join(os.path.dirname(__file__), "icons")
+
+
 class VIEW_WT_POV_plane_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addplane"
     bl_label = "Add POV plane"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a plane of infinite dimension for POV"
-    )
+
+    bl_description = "add a plane of infinite dimension for POV"
     bl_icon = os.path.join(icon_path, "pov.add.infinite_plane")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addplane", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addplane", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_box_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addbox"
     bl_label = "Add POV box"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV box solid primitive"
-    )
+
+    bl_description = "add a POV box solid primitive"
     bl_icon = os.path.join(icon_path, "pov.add.box")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addbox", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addbox", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_sphere_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addsphere"
     bl_label = "Add POV sphere"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add an untesselated sphere for POV"
-    )
+
+    bl_description = "add an untesselated sphere for POV"
     bl_icon = os.path.join(icon_path, "pov.add.sphere")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addsphere", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addsphere", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_cylinder_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addcylinder"
     bl_label = "Add POV cylinder"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add an untesselated cylinder for POV"
-    )
+
+    bl_description = "add an untesselated cylinder for POV"
     bl_icon = os.path.join(icon_path, "pov.add.cylinder")
     bl_widget = None
     bl_keymap = (
-        ("pov.addcylinder", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addcylinder", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_cone_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addcone"
     bl_label = "Add POV cone"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add an untesselated cone for POV"
-    )
+
+    bl_description = "add an untesselated cone for POV"
     bl_icon = os.path.join(icon_path, "pov.add.cone")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addcone", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addcone", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_torus_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addtorus"
     bl_label = "Add POV torus"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add an untesselated torus for POV"
-    )
+
+    bl_description = "add an untesselated torus for POV"
     bl_icon = os.path.join(icon_path, "pov.add.torus")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addtorus", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addtorus", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_rainbow_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addrainbow"
     bl_label = "Add POV rainbow"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV rainbow primitive"
-    )
+
+    bl_description = "add a POV rainbow primitive"
     bl_icon = os.path.join(icon_path, "pov.add.rainbow")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addrainbow", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addrainbow", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_lathe_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addlathe"
     bl_label = "Add POV lathe"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV lathe primitive"
-    )
+
+    bl_description = "add a POV lathe primitive"
     bl_icon = os.path.join(icon_path, "pov.add.lathe")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addlathe", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addlathe", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_prism_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addprism"
     bl_label = "Add POV prism"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV prism primitive"
-    )
+
+    bl_description = "add a POV prism primitive"
     bl_icon = os.path.join(icon_path, "pov.add.prism")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addprism", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addprism", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_heightfield_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addheightfield"
     bl_label = "Add POV heightfield"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV heightfield primitive"
-    )
+
+    bl_description = "add a POV heightfield primitive"
     bl_icon = os.path.join(icon_path, "pov.add.heightfield")
     bl_widget = None
     bl_keymap = (
-        ("pov.addheightfield", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addheightfield", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_superellipsoid_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addsuperellipsoid"
     bl_label = "Add POV superquadric ellipsoid"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV superquadric ellipsoid primitive"
-    )
+
+    bl_description = "add a POV superquadric ellipsoid primitive"
     bl_icon = os.path.join(icon_path, "pov.add.superellipsoid")
     bl_widget = None
     bl_keymap = (
-        ("pov.addsuperellipsoid", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addsuperellipsoid", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_spheresweep_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addspheresweep"
     bl_label = "Add POV spheresweep"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV spheresweep primitive"
-    )
+
+    bl_description = "add a POV spheresweep primitive"
     bl_icon = os.path.join(icon_path, "pov.add.spheresweep")
     bl_widget = None
     bl_keymap = (
-        ("pov.addspheresweep", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addspheresweep", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_loft_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addloft"
     bl_label = "Add POV loft macro"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV loft macro between editable spline cross sections"
-    )
+
+    bl_description = "add a POV loft macro between editable spline cross sections"
     bl_icon = os.path.join(icon_path, "pov.add.loft")
     bl_widget = None
-    bl_keymap = (
-        ("pov.addloft", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+    bl_keymap = (("pov.addloft", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),)
 
 
 class VIEW_WT_POV_polytocircle_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
-    bl_idname = "pov.addpolytocircle"
+    bl_idname = "pov.addpolygontocircle"
     bl_label = "Add POV poly to circle macro"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV regular polygon to circle blending macro"
-    )
-    bl_icon = os.path.join(icon_path, "pov.add.polytocircle")
+
+    bl_description = "add a POV regular polygon to circle blending macro"
+    bl_icon = os.path.join(icon_path, "pov.add.polygontocircle")
     bl_widget = None
     bl_keymap = (
-        ("pov.addpolytocircle", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addpolygontocircle", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_parametric_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addparametric"
     bl_label = "Add POV parametric surface"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV parametric surface primitive shaped from three equations (for x, y, z directions)"
-    )
+
+    bl_description = "add a POV parametric surface primitive shaped from three equations (for x, y, z directions)"
     bl_icon = os.path.join(icon_path, "pov.add.parametric")
     bl_widget = None
     bl_keymap = (
-        ("pov.addparametric", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addparametric", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_isosurface_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addisosurface"
     bl_label = "Add POV generic isosurface"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV generic shaped isosurface primitive"
-    )
+
+    bl_description = "add a POV generic shaped isosurface primitive"
     bl_icon = os.path.join(icon_path, "pov.add.isosurface")
     bl_widget = None
     bl_keymap = (
-        ("pov.addisosurface", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addisosurface", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_isosurfacebox_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addisosurfacebox"
     bl_label = "Add POV isosurface box"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV box shaped isosurface primitive"
-    )
+
+    bl_description = "add a POV box shaped isosurface primitive"
     bl_icon = os.path.join(icon_path, "pov.add.isosurfacebox")
     bl_widget = None
     bl_keymap = (
-        ("pov.addisosurfacebox", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addisosurfacebox", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_isosurfacesphere_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addisosurfacesphere"
     bl_label = "Add POV isosurface sphere"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV sphere shaped isosurface primitive"
-    )
+
+    bl_description = "add a POV sphere shaped isosurface primitive"
     bl_icon = os.path.join(icon_path, "pov.add.isosurfacesphere")
     bl_widget = None
     bl_keymap = (
-        ("pov.addisosurfacesphere", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addisosurfacesphere", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_isosurfacesupertorus_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addsupertorus"
     bl_label = "Add POV isosurface supertorus"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV torus shaped isosurface primitive"
-    )
+
+    bl_description = "add a POV torus shaped isosurface primitive"
     bl_icon = os.path.join(icon_path, "pov.add.isosurfacesupertorus")
     bl_widget = None
     bl_keymap = (
-        ("pov.addsupertorus", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addsupertorus", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_blobsphere_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addblobsphere"
     bl_label = "Add POV blob sphere"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV sphere shaped blob primitive"
-    )
+
+    bl_description = "add a POV sphere shaped blob primitive"
     bl_icon = os.path.join(icon_path, "pov.add.blobsphere")
     bl_widget = None
     bl_keymap = (
-        ("pov.addblobsphere", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addblobsphere", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_blobcapsule_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addblobcapsule"
     bl_label = "Add POV blob capsule"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV capsule shaped blob primitive"
-    )
+
+    bl_description = "add a POV capsule shaped blob primitive"
     bl_icon = os.path.join(icon_path, "pov.add.blobcapsule")
     bl_widget = None
     bl_keymap = (
-        ("pov.addblobcapsule", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addblobcapsule", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_blobplane_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addblobplane"
     bl_label = "Add POV blob plane"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV plane shaped blob primitive"
-    )
+
+    bl_description = "add a POV plane shaped blob primitive"
     bl_icon = os.path.join(icon_path, "pov.add.blobplane")
     bl_widget = None
     bl_keymap = (
-        ("pov.addblobplane", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addblobplane", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_blobellipsoid_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addblobellipsoid"
     bl_label = "Add POV blob ellipsoid"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV ellipsoid shaped blob primitive"
-    )
+
+    bl_description = "add a POV ellipsoid shaped blob primitive"
     bl_icon = os.path.join(icon_path, "pov.add.blobellipsoid")
     bl_widget = None
     bl_keymap = (
-        ("pov.addblobellipsoid", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addblobellipsoid", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 class VIEW_WT_POV_blobcube_add(WorkSpaceTool):
-    bl_space_type='VIEW_3D'
-    bl_context_mode='OBJECT'
+    bl_space_type = "VIEW_3D"
+    bl_context_mode = "OBJECT"
 
     # The prefix of the idname should be your add-on name.
     bl_idname = "pov.addsblobcube"
     bl_label = "Add POV blob cube"
-    bl_options = {'REGISTER', 'UNDO'}
-    bl_description = (
-        "add a POV cube shaped blob primitive"
-    )
+
+    bl_description = "add a POV cube shaped blob primitive"
     bl_icon = os.path.join(icon_path, "pov.add.blobcube")
     bl_widget = None
     bl_keymap = (
-        ("pov.addblobcube", {"type": 'LEFTMOUSE', "value": 'PRESS'},
-    {"properties": None}),
-        )
+        ("pov.addblobcube", {"type": "LEFTMOUSE", "value": "PRESS"}, {"properties": None}),
+    )
 
 
 classes = (
@@ -1208,6 +1113,7 @@ tool_classes = (
     VIEW_WT_POV_blobcube_add,
 )
 
+
 def register():
     for cls in classes:
         register_class(cls)
@@ -1216,7 +1122,9 @@ def register():
     last_tool = {"builtin.measure"}
     for index, wtl in enumerate(tool_classes):
         # Only separate first and 12th tools and hide subtools only in 8th (isosurfaces)
-        register_tool(wtl, after=last_tool, separator=index in [0,7,11,12,14,19], group=index == 15)
+        register_tool(
+            wtl, after=last_tool, separator=index in {0, 7, 11, 12, 14, 19}, group=index == 15
+        )
         last_tool = {wtl.bl_idname}
 
     bpy.types.VIEW3D_MT_add.prepend(menu_func_add)
diff --git a/render_povray/model_meta_topology.py b/render_povray/model_meta_topology.py
new file mode 100644
index 000000000..6a22be120
--- /dev/null
+++ b/render_povray/model_meta_topology.py
@@ -0,0 +1,306 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""Translate Blender meta balls to POV blobs."""
+
+import bpy
+from .shading import write_object_material_interior
+
+def export_meta(file, metas, tab_write, DEF_MAT_NAME):
+    """write all POV blob primitives and Blender Metas to exported file """
+    # TODO - blenders 'motherball' naming is not supported.
+
+    from .render import (
+        safety,
+        global_matrix,
+        write_matrix,
+        comments,
+    )
+    if comments and len(metas) >= 1:
+        file.write("//--Blob objects--\n\n")
+    # Get groups of metaballs by blender name prefix.
+    meta_group = {}
+    meta_elems = {}
+    for meta_ob in metas:
+        prefix = meta_ob.name.split(".")[0]
+        if prefix not in meta_group:
+            meta_group[prefix] = meta_ob  # .data.threshold
+        elems = [
+            (elem, meta_ob)
+            for elem in meta_ob.data.elements
+            if elem.type in {'BALL', 'ELLIPSOID', 'CAPSULE', 'CUBE', 'PLANE'}
+        ]
+        if prefix in meta_elems:
+            meta_elems[prefix].extend(elems)
+        else:
+            meta_elems[prefix] = elems
+
+        # empty metaball
+        if not elems:
+            tab_write(file, "\n//dummy sphere to represent empty meta location\n")
+            tab_write(file,
+                      "sphere {<%.6g, %.6g, %.6g>,0 pigment{rgbt 1} "
+                      "no_image no_reflection no_radiosity "
+                      "photons{pass_through collect off} hollow}\n\n"
+                      % (meta_ob.location.x, meta_ob.location.y, meta_ob.location.z)
+                      )  # meta_ob.name > povdataname)
+            return
+
+        # other metaballs
+
+        for mg, mob in meta_group.items():
+            if len(meta_elems[mg]) != 0:
+                tab_write(file, "blob{threshold %.4g // %s \n" % (mob.data.threshold, mg))
+                for elems in meta_elems[mg]:
+                    elem = elems[0]
+                    loc = elem.co
+                    stiffness = elem.stiffness
+                    if elem.use_negative:
+                        stiffness = -stiffness
+                    if elem.type == 'BALL':
+                        tab_write(file,
+                                  "sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g "
+                                  % (loc.x, loc.y, loc.z, elem.radius, stiffness)
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+                    elif elem.type == 'ELLIPSOID':
+                        tab_write(file,
+                                  "sphere{ <%.6g, %.6g, %.6g>,%.4g,%.4g "
+                                  % (
+                                      loc.x / elem.size_x,
+                                      loc.y / elem.size_y,
+                                      loc.z / elem.size_z,
+                                      elem.radius,
+                                      stiffness,
+                                  )
+                                  )
+                        tab_write(file,
+                                  "scale <%.6g, %.6g, %.6g>"
+                                  % (elem.size_x, elem.size_y, elem.size_z)
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+                    elif elem.type == 'CAPSULE':
+                        tab_write(file,
+                                  "cylinder{ <%.6g, %.6g, %.6g>,<%.6g, %.6g, %.6g>,%.4g,%.4g "
+                                  % (
+                                      (loc.x - elem.size_x),
+                                      loc.y,
+                                      loc.z,
+                                      (loc.x + elem.size_x),
+                                      loc.y,
+                                      loc.z,
+                                      elem.radius,
+                                      stiffness,
+                                  )
+                                  )
+                        # tab_write(file, "scale <%.6g, %.6g, %.6g>" % (elem.size_x, elem.size_y, elem.size_z))
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+
+                    elif elem.type == 'CUBE':
+                        tab_write(file,
+                                  "cylinder { -x*8, +x*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale  <1/4,1,1> scale <%.6g, %.6g, %.6g>\n"
+                                  % (
+                                      elem.radius * 2.0,
+                                      stiffness / 4.0,
+                                      loc.x,
+                                      loc.y,
+                                      loc.z,
+                                      elem.size_x,
+                                      elem.size_y,
+                                      elem.size_z,
+                                  )
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+                        tab_write(file,
+                                  "cylinder { -y*8, +y*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1/4,1> scale <%.6g, %.6g, %.6g>\n"
+                                  % (
+                                      elem.radius * 2.0,
+                                      stiffness / 4.0,
+                                      loc.x,
+                                      loc.y,
+                                      loc.z,
+                                      elem.size_x,
+                                      elem.size_y,
+                                      elem.size_z,
+                                  )
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+                        tab_write(file,
+                                  "cylinder { -z*8, +z*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1,1/4> scale <%.6g, %.6g, %.6g>\n"
+                                  % (
+                                      elem.radius * 2.0,
+                                      stiffness / 4.0,
+                                      loc.x,
+                                      loc.y,
+                                      loc.z,
+                                      elem.size_x,
+                                      elem.size_y,
+                                      elem.size_z,
+                                  )
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+
+                    elif elem.type == 'PLANE':
+                        tab_write(file,
+                                  "cylinder { -x*8, +x*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale  <1/4,1,1> scale <%.6g, %.6g, %.6g>\n"
+                                  % (
+                                      elem.radius * 2.0,
+                                      stiffness / 4.0,
+                                      loc.x,
+                                      loc.y,
+                                      loc.z,
+                                      elem.size_x,
+                                      elem.size_y,
+                                      elem.size_z,
+                                  )
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+                        tab_write(file,
+                                  "cylinder { -y*8, +y*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1/4,1> scale <%.6g, %.6g, %.6g>\n"
+                                  % (
+                                      elem.radius * 2.0,
+                                      stiffness / 4.0,
+                                      loc.x,
+                                      loc.y,
+                                      loc.z,
+                                      elem.size_x,
+                                      elem.size_y,
+                                      elem.size_z,
+                                  )
+                                  )
+                        write_matrix(file, global_matrix @ elems[1].matrix_world)
+                        tab_write(file, "}\n")
+
+                try:
+                    one_material = elems[1].data.materials[
+                        0
+                    ]  # lame! - blender cant do enything else.
+                except BaseException as e:
+                    print(e.__doc__)
+                    print('An exception occurred: {}'.format(e))
+                    one_material = None
+                if one_material:
+                    diffuse_color = one_material.diffuse_color
+                    trans = 1.0 - one_material.pov.alpha
+                    if (
+                            one_material.pov.use_transparency
+                            and one_material.pov.transparency_method == 'RAYTRACE'
+                    ):
+                        pov_filter = one_material.pov_raytrace_transparency.filter * (
+                                1.0 - one_material.pov.alpha
+                        )
+                        trans = (1.0 - one_material.pov.alpha) - pov_filter
+                    else:
+                        pov_filter = 0.0
+                    material_finish = material_names_dictionary[one_material.name]
+                    tab_write(file,
+                              "pigment {srgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} \n"
+                              % (
+                                  diffuse_color[0],
+                                  diffuse_color[1],
+                                  diffuse_color[2],
+                                  pov_filter,
+                                  trans,
+                              )
+                              )
+                    tab_write(file, "finish{%s} " % safety(material_finish, ref_level_bound=2))
+                else:
+                    material_finish = DEF_MAT_NAME
+                    trans = 0.0
+                    tab_write(file,
+                              "pigment{srgbt<1,1,1,%.3g>} finish{%s} "
+                              % (trans, safety(material_finish, ref_level_bound=2))
+                              )
+
+                    write_object_material_interior(file, one_material, mob, tab_write)
+                    # write_object_material_interior(file, one_material, elems[1])
+                    tab_write(file, "radiosity{importance %3g}\n" % mob.pov.importance_value)
+                    tab_write(file, "}\n\n")  # End of Metaball block
+
+
+'''
+        meta = ob.data
+
+        # important because no elements will break parsing.
+        elements = [elem for elem in meta.elements if elem.type in {'BALL', 'ELLIPSOID'}]
+
+        if elements:
+            tab_write(file, "blob {\n")
+            tab_write(file, "threshold %.4g\n" % meta.threshold)
+            importance = ob.pov.importance_value
+
+            try:
+                material = meta.materials[0]  # lame! - blender cant do enything else.
+            except:
+                material = None
+
+            for elem in elements:
+                loc = elem.co
+
+                stiffness = elem.stiffness
+                if elem.use_negative:
+                    stiffness = - stiffness
+
+                if elem.type == 'BALL':
+
+                    tab_write(file, "sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g }\n" %
+                             (loc.x, loc.y, loc.z, elem.radius, stiffness))
+
+                    # After this wecould do something simple like...
+                    #     "pigment {Blue} }"
+                    # except we'll write the color
+
+                elif elem.type == 'ELLIPSOID':
+                    # location is modified by scale
+                    tab_write(file, "sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g }\n" %
+                             (loc.x / elem.size_x,
+                              loc.y / elem.size_y,
+                              loc.z / elem.size_z,
+                              elem.radius, stiffness))
+                    tab_write(file, "scale <%.6g, %.6g, %.6g> \n" %
+                             (elem.size_x, elem.size_y, elem.size_z))
+
+            if material:
+                diffuse_color = material.diffuse_color
+                trans = 1.0 - material.pov.alpha
+                if material.pov.use_transparency and material.pov.transparency_method == 'RAYTRACE':
+                    pov_filter = material.pov_raytrace_transparency.filter * (1.0 - 
+                    material.pov.alpha)
+                    trans = (1.0 - material.pov.alpha) - pov_filter
+                else:
+                    pov_filter = 0.0
+
+                material_finish = material_names_dictionary[material.name]
+
+                tab_write(file, "pigment {srgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} \n" %
+                         (diffuse_color[0], diffuse_color[1], diffuse_color[2],
+                          pov_filter, trans))
+                tab_write(file, "finish {%s}\n" % safety(material_finish, ref_level_bound=2))
+
+            else:
+                tab_write(file, "pigment {srgb 1} \n")
+                # Write the finish last.
+                tab_write(file, "finish {%s}\n" % (safety(DEF_MAT_NAME, ref_level_bound=2)))
+
+            write_object_material_interior(file, material, elems[1], tab_write)
+
+            write_matrix(file, global_matrix @ ob.matrix_world)
+            # Importance for radiosity sampling added here
+            tab_write(file, "radiosity { \n")
+            # importance > ob.pov.importance_value
+            tab_write(file, "importance %3g \n" % importance)
+            tab_write(file, "}\n")
+
+            tab_write(file, "}\n")  # End of Metaball block
+
+            if comments and len(metas) >= 1:
+                file.write("\n")
+'''
diff --git a/render_povray/model_poly_topology.py b/render_povray/model_poly_topology.py
new file mode 100644
index 000000000..f6d400a29
--- /dev/null
+++ b/render_povray/model_poly_topology.py
@@ -0,0 +1,719 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""Translate to POV the control point compound geometries.
+
+Here polygon meshes as POV mesh2 objects.
+"""
+
+import bpy
+from . import texturing
+from .scenography import image_format, img_map, img_map_transforms
+from .shading import write_object_material_interior
+from .model_primitives import write_object_modifiers
+
+def write_object_csg_inside_vector(ob, file):
+    """Write inside vector for use by pov CSG, only once per object using boolean"""
+    has_csg_inside_vector = False
+    for modif in ob.modifiers:
+        if not has_csg_inside_vector and modif.type == "BOOLEAN" and ob.pov.boolean_mod == "POV":
+            file.write(
+                "\tinside_vector <%.6g, %.6g, %.6g>\n"
+                % (
+                    ob.pov.inside_vector[0],
+                    ob.pov.inside_vector[1],
+                    ob.pov.inside_vector[2],
+                )
+            )
+            has_csg_inside_vector = True
+
+def export_mesh(file,
+                ob,
+                povdataname,
+                material_names_dictionary,
+                unpacked_images,
+                tab_level,
+                tab_write,
+                linebreaksinlists):
+    from .render import (
+        string_strip_hyphen,
+        tab,
+        comments,
+        preview_dir,
+    )
+
+    ob_eval = ob  # not sure this is needed in case to_mesh_clear could damage obj ?
+    importance = ob.pov.importance_value
+
+    try:
+        me = ob_eval.to_mesh()
+
+    # Here identify the exception for mesh object with no data: Runtime-Error ?
+    # So we can write something for the dataname or maybe treated "if not me" below
+    except BaseException as e:
+        print(e.__doc__)
+        print("An exception occurred: {}".format(e))
+        # also happens when curves cant be made into meshes because of no-data
+        return False  # To continue object loop
+    if me:
+        me.calc_loop_triangles()
+        me_materials = me.materials
+        me_faces = me.loop_triangles[:]
+        # --- numpytest
+        # me_looptris = me.loops
+
+        # Below otypes = ['int32'] is a 32-bit signed integer number numpy datatype
+        # get_v_index = np.vectorize(lambda l: l.vertex_index, otypes = ['int32'], cache = True)
+        # faces_verts_idx = get_v_index(me_looptris)
+
+    # if len(me_faces)==0:
+    # tab_write(file, "\n//dummy sphere to represent empty mesh 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)
+
+    if not me or not me_faces:
+        tab_write(file, "\n//dummy sphere to represent empty mesh 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,
+        )
+        return False  # To continue object loop
+
+    uv_layers = me.uv_layers
+    if len(uv_layers) > 0:
+        if me.uv_layers.active and uv_layers.active.data:
+            uv_layer = uv_layers.active.data
+    else:
+        uv_layer = None
+
+    try:
+        # vcol_layer = me.vertex_colors.active.data
+        vcol_layer = me.vertex_colors.active.data
+    except AttributeError:
+        vcol_layer = None
+
+    faces_verts = [f.vertices[:] for f in me_faces]
+    faces_normals = [f.normal[:] for f in me_faces]
+    verts_normals = [v.normal[:] for v in me.vertices]
+
+    # Use named declaration to allow reference e.g. for baking. MR
+    file.write("\n")
+    tab_write(file, "#declare %s =\n" % povdataname)
+    tab_write(file, "mesh2 {\n")
+    tab_write(file, "vertex_vectors {\n")
+    tab_write(file, "%d" % len(me.vertices))  # vert count
+
+    tab_str = tab * tab_level
+    for v in me.vertices:
+        if linebreaksinlists:
+            file.write(",\n")
+            file.write(tab_str + "<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
+        else:
+            file.write(", ")
+            file.write("<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
+        # tab_write(file, "<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
+    file.write("\n")
+    tab_write(file, "}\n")
+
+    # Build unique Normal list
+    uniqueNormals = {}
+    for fi, f in enumerate(me_faces):
+        fv = faces_verts[fi]
+        # [-1] is a dummy index, use a list so we can modify in place
+        if f.use_smooth:  # Use vertex normals
+            for v in fv:
+                key = verts_normals[v]
+                uniqueNormals[key] = [-1]
+        else:  # Use face normal
+            key = faces_normals[fi]
+            uniqueNormals[key] = [-1]
+
+    tab_write(file, "normal_vectors {\n")
+    tab_write(file, "%d" % len(uniqueNormals))  # vert count
+    idx = 0
+    tab_str = tab * tab_level
+    for no, index in uniqueNormals.items():
+        if linebreaksinlists:
+            file.write(",\n")
+            file.write(tab_str + "<%.6f, %.6f, %.6f>" % no)  # vert count
+        else:
+            file.write(", ")
+            file.write("<%.6f, %.6f, %.6f>" % no)  # vert count
+        index[0] = idx
+        idx += 1
+    file.write("\n")
+    tab_write(file, "}\n")
+    # Vertex colors
+    vertCols = {}  # Use for material colors also.
+
+    if uv_layer:
+        # Generate unique UV's
+        uniqueUVs = {}
+        # n = 0
+        for f in me_faces:  # me.faces in 2.7
+            uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]
+
+            for uv in uvs:
+                uniqueUVs[uv[:]] = [-1]
+
+        tab_write(file, "uv_vectors {\n")
+        # print unique_uvs
+        tab_write(file, "%d" % len(uniqueUVs))  # vert count
+        idx = 0
+        tab_str = tab * tab_level
+        for uv, index in uniqueUVs.items():
+            if linebreaksinlists:
+                file.write(",\n")
+                file.write(tab_str + "<%.6f, %.6f>" % uv)
+            else:
+                file.write(", ")
+                file.write("<%.6f, %.6f>" % uv)
+            index[0] = idx
+            idx += 1
+        """
+        else:
+            # Just add 1 dummy vector, no real UV's
+            tab_write(file, '1') # vert count
+            file.write(',\n\t\t<0.0, 0.0>')
+        """
+        file.write("\n")
+        tab_write(file, "}\n")
+    if me.vertex_colors:
+        # Write down vertex colors as a texture for each vertex
+        tab_write(file, "texture_list {\n")
+        tab_write(
+            file, "%d\n" % (len(me_faces) * 3)
+        )  # assumes we have only triangles
+        VcolIdx = 0
+        if comments:
+            file.write(
+                "\n  //Vertex colors: one simple pigment texture per vertex\n"
+            )
+        for fi, f in enumerate(me_faces):
+            # annoying, index may be invalid
+            material_index = f.material_index
+            try:
+                material = me_materials[material_index]
+            except BaseException as e:
+                print(e.__doc__)
+                print("An exception occurred: {}".format(e))
+                material = None
+            if (
+                material
+                # and material.pov.use_vertex_color_paint
+            ):  # Or maybe Always use vertex color when there is some for now
+
+                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]
+
+                for col in cols:
+                    key = (
+                        col[0],
+                        col[1],
+                        col[2],
+                        material_index,
+                    )  # Material index!
+                    VcolIdx += 1
+                    vertCols[key] = [VcolIdx]
+                    if linebreaksinlists:
+                        tab_write(
+                            file,
+                            "texture {pigment{ color srgb <%6f,%6f,%6f> }}\n"
+                            % (col[0], col[1], col[2]),
+                        )
+                    else:
+                        tab_write(
+                            file,
+                            "texture {pigment{ color srgb <%6f,%6f,%6f> }}"
+                            % (col[0], col[1], col[2]),
+                        )
+                        tab_str = tab * tab_level
+            else:
+                if material:
+                    # Multiply diffuse with SSS Color
+                    if material.pov_subsurface_scattering.use:
+                        diffuse_color = [
+                            i * j
+                            for i, j in zip(
+                                material.pov_subsurface_scattering.color[:],
+                                material.diffuse_color[:],
+                            )
+                        ]
+                        key = (
+                            diffuse_color[0],
+                            diffuse_color[1],
+                            diffuse_color[2],
+                            material_index,
+                        )
+                        vertCols[key] = [-1]
+                    else:
+                        diffuse_color = material.diffuse_color[:]
+                        key = (
+                            diffuse_color[0],
+                            diffuse_color[1],
+                            diffuse_color[2],
+                            material_index,
+                        )
+                        vertCols[key] = [-1]
+
+        tab_write(file, "\n}\n")
+        # Face indices
+        tab_write(file, "\nface_indices {\n")
+        tab_write(file, "%d" % (len(me_faces)))  # faces count
+        tab_str = tab * tab_level
+
+        for fi, f in enumerate(me_faces):
+            fv = faces_verts[fi]
+            material_index = f.material_index
+
+            if vcol_layer:
+                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]
+
+            if not me_materials or (
+                me_materials[material_index] is None
+            ):  # No materials
+                if linebreaksinlists:
+                    file.write(",\n")
+                    # vert count
+                    file.write(tab_str + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
+                else:
+                    file.write(", ")
+                    file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2]))  # vert count
+            else:
+                material = me_materials[material_index]
+                if me.vertex_colors:  # and material.pov.use_vertex_color_paint:
+                    # Color per vertex - vertex color
+
+                    col1 = cols[0]
+                    col2 = cols[1]
+                    col3 = cols[2]
+
+                    ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
+                    ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
+                    ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
+                else:
+                    # Color per material - flat material color
+                    if material.pov_subsurface_scattering.use:
+                        diffuse_color = [
+                            i * j
+                            for i, j in zip(
+                                material.pov_subsurface_scattering.color[:],
+                                material.diffuse_color[:],
+                            )
+                        ]
+                    else:
+                        diffuse_color = material.diffuse_color[:]
+                    ci1 = ci2 = ci3 = vertCols[
+                        diffuse_color[0],
+                        diffuse_color[1],
+                        diffuse_color[2],
+                        f.material_index,
+                    ][0]
+                    # ci are zero based index so we'll subtract 1 from them
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str
+                        + "<%d,%d,%d>, %d,%d,%d"
+                        % (
+                            fv[0],
+                            fv[1],
+                            fv[2],
+                            ci1 - 1,
+                            ci2 - 1,
+                            ci3 - 1,
+                        )
+                    )  # vert count
+                else:
+                    file.write(", ")
+                    file.write(
+                        "<%d,%d,%d>, %d,%d,%d"
+                        % (
+                            fv[0],
+                            fv[1],
+                            fv[2],
+                            ci1 - 1,
+                            ci2 - 1,
+                            ci3 - 1,
+                        )
+                    )  # vert count
+
+        file.write("\n")
+        tab_write(file, "}\n")
+
+        # normal_indices indices
+        tab_write(file, "normal_indices {\n")
+        tab_write(file, "%d" % (len(me_faces)))  # faces count
+        tab_str = tab * tab_level
+        for fi, fv in enumerate(faces_verts):
+
+            if me_faces[fi].use_smooth:
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str
+                        + "<%d,%d,%d>"
+                        % (
+                            uniqueNormals[verts_normals[fv[0]]][0],
+                            uniqueNormals[verts_normals[fv[1]]][0],
+                            uniqueNormals[verts_normals[fv[2]]][0],
+                        )
+                    )  # vert count
+                else:
+                    file.write(", ")
+                    file.write(
+                        "<%d,%d,%d>"
+                        % (
+                            uniqueNormals[verts_normals[fv[0]]][0],
+                            uniqueNormals[verts_normals[fv[1]]][0],
+                            uniqueNormals[verts_normals[fv[2]]][0],
+                        )
+                    )  # vert count
+            else:
+                idx = uniqueNormals[faces_normals[fi]][0]
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str + "<%d,%d,%d>" % (idx, idx, idx)
+                    )  # vert count
+                else:
+                    file.write(", ")
+                    file.write("<%d,%d,%d>" % (idx, idx, idx))  # vert count
+
+        file.write("\n")
+        tab_write(file, "}\n")
+
+        if uv_layer:
+            tab_write(file, "uv_indices {\n")
+            tab_write(file, "%d" % (len(me_faces)))  # faces count
+            tab_str = tab * tab_level
+            for f in me_faces:
+                uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]
+
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str
+                        + "<%d,%d,%d>"
+                        % (
+                            uniqueUVs[uvs[0]][0],
+                            uniqueUVs[uvs[1]][0],
+                            uniqueUVs[uvs[2]][0],
+                        )
+                    )
+                else:
+                    file.write(", ")
+                    file.write(
+                        "<%d,%d,%d>"
+                        % (
+                            uniqueUVs[uvs[0]][0],
+                            uniqueUVs[uvs[1]][0],
+                            uniqueUVs[uvs[2]][0],
+                        )
+                    )
+
+            file.write("\n")
+            tab_write(file, "}\n")
+
+        # XXX BOOLEAN MODIFIER
+        write_object_csg_inside_vector(ob, file)
+
+        if me.materials:
+            try:
+                material = me.materials[0]  # dodgy
+                write_object_material_interior(file, material, ob, tab_write)
+            except IndexError:
+                print(me)
+
+        # POV object modifiers such as
+        # hollow / sturm / double_illuminate etc.
+        write_object_modifiers(ob, file)
+
+        # Importance for radiosity sampling added here:
+        tab_write(file, "radiosity { \n")
+        tab_write(file, "importance %3g \n" % importance)
+        tab_write(file, "}\n")
+
+        tab_write(file, "}\n")  # End of mesh block
+    else:
+        facesMaterials = []  # WARNING!!!!!!!!!!!!!!!!!!!!!!
+        if me_materials:
+            new_me_faces_mat_idx = (f for f in me_faces if f.material_index not in
+                                       facesMaterials)
+            for f in new_me_faces_mat_idx:
+                facesMaterials.append(f.material_index)
+        # No vertex colors, so write material colors as vertex colors
+
+        for i, material in enumerate(me_materials):
+            if (
+                material and material.pov.material_use_nodes is False
+            ):  # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                # Multiply diffuse with SSS Color
+                if material.pov_subsurface_scattering.use:
+                    diffuse_color = [
+                        i * j
+                        for i, j in zip(
+                            material.pov_subsurface_scattering.color[:],
+                            material.diffuse_color[:],
+                        )
+                    ]
+                    key = (
+                        diffuse_color[0],
+                        diffuse_color[1],
+                        diffuse_color[2],
+                        i,
+                    )  # i == f.mat
+                    vertCols[key] = [-1]
+                else:
+                    diffuse_color = material.diffuse_color[:]
+                    key = (
+                        diffuse_color[0],
+                        diffuse_color[1],
+                        diffuse_color[2],
+                        i,
+                    )  # i == f.mat
+                    vertCols[key] = [-1]
+
+                idx = 0
+                texturing.local_material_names = []
+                for col, index in vertCols.items():
+                    # if me_materials:
+                    mater = me_materials[col[3]]
+                    if me_materials is not None:
+                        texturing.write_texture_influence(
+                            file,
+                            mater,
+                            material_names_dictionary,
+                            image_format,
+                            img_map,
+                            img_map_transforms,
+                            tab_write,
+                            comments,
+                            col,
+                            preview_dir,
+                            unpacked_images,
+                        )
+                    # ------------------------------------------------
+                    index[0] = idx
+                    idx += 1
+
+        # Vert Colors
+        tab_write(file, "texture_list {\n")
+        # In case there's is no material slot, give at least one texture
+        # (an empty one so it uses pov default)
+        if len(vertCols) != 0:
+            tab_write(
+                file, "%s" % (len(vertCols))
+            )  # vert count
+        else:
+            tab_write(file, "1")
+        # below "material" alias, added check obj.active_material
+        # to avoid variable referenced before assignment error
+        try:
+            material = ob.active_material
+        except IndexError:
+            # when no material slot exists,
+            material = None
+
+        # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+        if (
+            material
+            and ob.active_material is not None
+            and not material.pov.material_use_nodes
+            and not material.use_nodes
+        ):
+            if material.pov.replacement_text != "":
+                file.write("\n")
+                file.write(" texture{%s}\n" % material.pov.replacement_text)
+
+            else:
+                # Loop through declared materials list
+                # global local_material_names
+                for cMN in texturing.local_material_names:
+                    if material != "Default":
+                        file.write("\n texture{MAT_%s}\n" % cMN)
+                        # use string_strip_hyphen(material_names_dictionary[material]))
+                        # or Something like that to clean up the above?
+        elif material and material.pov.material_use_nodes:
+            for index in facesMaterials:
+                faceMaterial = string_strip_hyphen(
+                    bpy.path.clean_name(me_materials[index].name)
+                )
+                file.write("\n texture{%s}\n" % faceMaterial)
+        # END!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+        elif vertCols:
+            for cMN in vertCols: # or in texturing.local_material_names:
+                # if possible write only one, though
+                file.write(" texture{}\n")
+        else:
+            file.write(" texture{}\n")
+        tab_write(file, "}\n")
+
+        # Face indices
+        tab_write(file, "face_indices {\n")
+        tab_write(file, "%d" % (len(me_faces)))  # faces count
+        tab_str = tab * tab_level
+
+        for fi, f in enumerate(me_faces):
+            fv = faces_verts[fi]
+            material_index = f.material_index
+
+            if vcol_layer:
+                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]
+
+            if (
+                not me_materials or me_materials[material_index] is None
+            ):  # No materials
+                if linebreaksinlists:
+                    file.write(",\n")
+                    # vert count
+                    file.write(tab_str + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
+                else:
+                    file.write(", ")
+                    file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2]))  # vert count
+            else:
+                material = me_materials[material_index]
+                ci1 = ci2 = ci3 = f.material_index
+                if me.vertex_colors:  # and material.pov.use_vertex_color_paint:
+                    # Color per vertex - vertex color
+
+                    col1 = cols[0]
+                    col2 = cols[1]
+                    col3 = cols[2]
+
+                    ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
+                    ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
+                    ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
+                elif material.pov.material_use_nodes:
+                    ci1 = ci2 = ci3 = 0
+                else:
+                    # Color per material - flat material color
+                    if material.pov_subsurface_scattering.use:
+                        diffuse_color = [
+                            i * j
+                            for i, j in zip(
+                                material.pov_subsurface_scattering.color[:],
+                                material.diffuse_color[:],
+                            )
+                        ]
+                    else:
+                        diffuse_color = material.diffuse_color[:]
+                    ci1 = ci2 = ci3 = vertCols[
+                        diffuse_color[0],
+                        diffuse_color[1],
+                        diffuse_color[2],
+                        f.material_index,
+                    ][0]
+
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str
+                        + "<%d,%d,%d>, %d,%d,%d"
+                        % (fv[0], fv[1], fv[2], ci1, ci2, ci3)
+                    )  # vert count
+                else:
+                    file.write(", ")
+                    file.write(
+                        "<%d,%d,%d>, %d,%d,%d"
+                        % (fv[0], fv[1], fv[2], ci1, ci2, ci3)
+                    )  # vert count
+
+        file.write("\n")
+        tab_write(file, "}\n")
+
+        # normal_indices indices
+        tab_write(file, "normal_indices {\n")
+        tab_write(file, "%d" % (len(me_faces)))  # faces count
+        tab_str = tab * tab_level
+        for fi, fv in enumerate(faces_verts):
+            if me_faces[fi].use_smooth:
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str
+                        + "<%d,%d,%d>"
+                        % (
+                            uniqueNormals[verts_normals[fv[0]]][0],
+                            uniqueNormals[verts_normals[fv[1]]][0],
+                            uniqueNormals[verts_normals[fv[2]]][0],
+                        )
+                    )  # vert count
+                else:
+                    file.write(", ")
+                    file.write(
+                        "<%d,%d,%d>"
+                        % (
+                            uniqueNormals[verts_normals[fv[0]]][0],
+                            uniqueNormals[verts_normals[fv[1]]][0],
+                            uniqueNormals[verts_normals[fv[2]]][0],
+                        )
+                    )  # vert count
+            else:
+                idx = uniqueNormals[faces_normals[fi]][0]
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str + "<%d,%d,%d>" % (idx, idx, idx)
+                    )  # vertcount
+                else:
+                    file.write(", ")
+                    file.write("<%d,%d,%d>" % (idx, idx, idx))  # vert count
+
+        file.write("\n")
+        tab_write(file, "}\n")
+
+        if uv_layer:
+            tab_write(file, "uv_indices {\n")
+            tab_write(file, "%d" % (len(me_faces)))  # faces count
+            tab_str = tab * tab_level
+            for f in me_faces:
+                uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]
+
+                if linebreaksinlists:
+                    file.write(",\n")
+                    file.write(
+                        tab_str
+                        + "<%d,%d,%d>"
+                        % (
+                            uniqueUVs[uvs[0]][0],
+                            uniqueUVs[uvs[1]][0],
+                            uniqueUVs[uvs[2]][0],
+                        )
+                    )
+                else:
+                    file.write(", ")
+                    file.write(
+                        "<%d,%d,%d>"
+                        % (
+                            uniqueUVs[uvs[0]][0],
+                            uniqueUVs[uvs[1]][0],
+                            uniqueUVs[uvs[2]][0],
+                        )
+                    )
+
+            file.write("\n")
+            tab_write(file, "}\n")
+
+        # XXX BOOLEAN
+        write_object_csg_inside_vector(ob, file)
+        if me.materials:
+            try:
+                material = me.materials[0]  # dodgy
+                write_object_material_interior(file, material, ob, tab_write)
+            except IndexError:
+                print(me)
+
+        # POV object modifiers such as
+        # hollow / sturm / double_illuminate etc.
+        write_object_modifiers(ob, file)
+
+        # Importance for radiosity sampling added here:
+        tab_write(file, "radiosity { \n")
+        tab_write(file, "importance %3g \n" % importance)
+        tab_write(file, "}\n")
+
+        tab_write(file, "}\n")  # End of mesh block
+
+    ob_eval.to_mesh_clear()
+    return True
diff --git a/render_povray/model_primitives.py b/render_povray/model_primitives.py
new file mode 100644
index 000000000..16ca04e40
--- /dev/null
+++ b/render_povray/model_primitives.py
@@ -0,0 +1,796 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+""" Get POV-Ray specific objects In and Out of Blender """
+from math import pi, cos, sin
+import os.path
+import bpy
+from bpy_extras.object_utils import object_data_add
+from bpy_extras.io_utils import ImportHelper
+from bpy.utils import register_class, unregister_class
+from bpy.types import Operator
+
+from bpy.props import (
+    StringProperty,
+    BoolProperty,
+    IntProperty,
+    FloatProperty,
+    FloatVectorProperty,
+    EnumProperty,
+)
+
+from mathutils import Vector, Matrix
+
+
+# import collections
+
+
+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 pov_define_mesh(mesh, verts, edges, faces, name, hide_geometry=True):
+    """Generate proxy mesh."""
+    if mesh is None:
+        mesh = bpy.data.meshes.new(name)
+    mesh.from_pydata(verts, edges, faces)
+    # Function Arguments change : now bpy.types.Mesh.update (calc_edges, calc_edges_loose,
+    # calc_loop_triangles), was (calc_edges, calc_tessface)
+
+
+    mesh.update()
+    mesh.validate(
+        verbose=False
+    )  # Set it to True to see debug messages (helps ensure you generate valid geometry).
+    if hide_geometry:
+        mesh.vertices.foreach_set("hide", [True] * len(mesh.vertices))
+        mesh.edges.foreach_set("hide", [True] * len(mesh.edges))
+        mesh.polygons.foreach_set("hide", [True] * len(mesh.polygons))
+    return mesh
+
+
+class POV_OT_plane_add(Operator):
+    """Add the representation of POV infinite plane using just a very big Blender Plane.
+
+    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
+    to keep proxy consistency by hiding edit geometry."""
+
+    bl_idname = "pov.addplane"
+    bl_label = "Plane"
+    bl_description = "Add Plane"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    def execute(self, context):
+        # layers = 20*[False]
+        # layers[0] = True
+        bpy.ops.mesh.primitive_plane_add(size=10000)
+        ob = context.object
+        ob.name = ob.data.name = "PovInfinitePlane"
+        bpy.ops.object.mode_set(mode="EDIT")
+        self.report(
+            {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
+        )
+        bpy.ops.mesh.hide(unselected=False)
+        bpy.ops.object.mode_set(mode="OBJECT")
+        bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "PLANE"
+        return {"FINISHED"}
+
+
+class POV_OT_box_add(Operator):
+    """Add the representation of POV box using a simple Blender mesh cube.
+
+    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
+    to keep proxy consistency by hiding edit geometry."""
+
+    bl_idname = "pov.addbox"
+    bl_label = "Box"
+    bl_description = "Add Box"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    def execute(self, context):
+        # layers = 20*[False]
+        # layers[0] = True
+        bpy.ops.mesh.primitive_cube_add()
+        ob = context.object
+        ob.name = ob.data.name = "PovBox"
+        bpy.ops.object.mode_set(mode="EDIT")
+        self.report(
+            {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
+        )
+        bpy.ops.mesh.hide(unselected=False)
+        bpy.ops.object.mode_set(mode="OBJECT")
+        ob.pov.object_as = "BOX"
+        return {"FINISHED"}
+
+
+def pov_cylinder_define(context, op, ob, radius, loc, loc_cap):
+    """Pick POV cylinder properties either from creation operator, import, or data update"""
+    if op:
+        cy_rad = op.cy_rad
+        loc = bpy.context.scene.cursor.location
+        loc_cap[0] = loc[0]
+        loc_cap[1] = loc[1]
+        loc_cap[2] = loc[2] + 2
+    vec = Vector(loc_cap) - Vector(loc)
+    depth = vec.length
+    rot = Vector((0, 0, 1)).rotation_difference(vec)  # Rotation from Z axis.
+    trans = rot @ Vector(
+        (0, 0, depth / 2)
+    )  # Such that origin is at center of the base of the cylinder.
+    roteuler = rot.to_euler()
+    if not ob:
+        bpy.ops.object.add(type="MESH", location=loc)
+        ob = context.object
+        ob.name = ob.data.name = "PovCylinder"
+        ob.pov.cylinder_radius = radius
+        ob.pov.cylinder_location_cap = vec
+        ob.data.use_auto_smooth = True
+        ob.pov.object_as = "CYLINDER"
+
+    else:
+        ob.location = loc
+
+    bpy.ops.object.mode_set(mode="EDIT")
+    bpy.ops.mesh.reveal()
+    bpy.ops.mesh.select_all(action="SELECT")
+    bpy.ops.mesh.delete(type="VERT")
+    bpy.ops.mesh.primitive_cylinder_add(
+        radius=radius, depth=depth, location=loc, rotation=roteuler, end_fill_type="NGON"
+    )  # 'NOTHING'
+    bpy.ops.transform.translate(value=trans)
+
+    bpy.ops.mesh.hide(unselected=False)
+    bpy.ops.object.mode_set(mode="OBJECT")
+    bpy.ops.object.shade_smooth()
+
+
+class POV_OT_cylinder_add(Operator):
+    """Add the representation of POV cylinder using pov_cylinder_define() function.
+
+    Use imported_cyl_loc when this operator is run by POV importer."""
+    bl_options = {'REGISTER', 'UNDO'}
+    bl_idname = "pov.addcylinder"
+    bl_label = "Cylinder"
+    bl_description = "Add Cylinder"
+
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    # Keep in sync within model_properties.py section Cylinder
+    # as this allows interactive update
+    cy_rad: FloatProperty(name="Cylinder radius", min=0.00, max=10.0, default=1.0)
+
+    imported_cyl_loc: FloatVectorProperty(
+        name="Imported Pov base location", precision=6, default=(0.0, 0.0, 0.0)
+    )
+
+    imported_cyl_loc_cap: FloatVectorProperty(
+        name="Imported Pov cap location", precision=6, default=(0.0, 0.0, 2.0)
+    )
+
+    def execute(self, context):
+        props = self.properties
+        cy_rad = props.cy_rad
+        if ob := context.object:
+            if ob.pov.imported_cyl_loc:
+                LOC = ob.pov.imported_cyl_loc
+            if ob.pov.imported_cyl_loc_cap:
+                LOC_CAP = ob.pov.imported_cyl_loc_cap
+        elif not props.imported_cyl_loc:
+            LOC_CAP = LOC = bpy.context.scene.cursor.location
+            LOC_CAP[2] += 2.0
+        else:
+            LOC = props.imported_cyl_loc
+            LOC_CAP = props.imported_cyl_loc_cap
+            self.report(
+                {"INFO"},
+                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
+            )
+
+        pov_cylinder_define(context, self, None, self.cy_rad, LOC, LOC_CAP)
+
+        return {"FINISHED"}
+
+
+class POV_OT_cylinder_update(Operator):
+    """Update the POV cylinder.
+
+    Delete its previous proxy geometry and rerun pov_cylinder_define() function
+    with the new parameters"""
+
+    bl_idname = "pov.cylinder_update"
+    bl_label = "Update"
+    bl_description = "Update Cylinder"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    @classmethod
+    def poll(cls, context):
+        engine = context.scene.render.engine
+        ob = context.object
+        return (
+            ob
+            and ob.data
+            and ob.type == "MESH"
+            and ob.pov.object_as == "CYLINDER"
+            and engine in cls.COMPAT_ENGINES
+        )
+
+    def execute(self, context):
+        ob = context.object
+        radius = ob.pov.cylinder_radius
+        loc = ob.location
+        loc_cap = loc + ob.pov.cylinder_location_cap
+
+        pov_cylinder_define(context, None, ob, radius, loc, loc_cap)
+
+        return {"FINISHED"}
+
+
+# ----------------------------------- SPHERE---------------------------------- #
+def pov_sphere_define(context, op, ob, loc):
+    """create the representation of POV sphere using a Blender icosphere.
+
+    Its nice platonic solid curvature better represents pov rendertime
+    tessellation than a UV sphere"""
+
+    if op:
+        sphe_rad = op.sphe_rad
+        loc = bpy.context.scene.cursor.location
+    else:
+        assert ob
+        sphe_rad = ob.pov.sphere_radius
+
+        # keep object rotation and location for the add object operator
+        obrot = ob.rotation_euler
+        # obloc = ob.location
+        obscale = ob.scale
+
+        bpy.ops.object.mode_set(mode="EDIT")
+        bpy.ops.mesh.reveal()
+        bpy.ops.mesh.select_all(action="SELECT")
+        bpy.ops.mesh.delete(type="VERT")
+        bpy.ops.mesh.primitive_ico_sphere_add(
+            subdivisions=4, radius=ob.pov.sphere_radius, location=loc, rotation=obrot
+        )
+        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
+        bpy.ops.transform.resize(value=obscale)
+        # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
+
+        bpy.ops.mesh.hide(unselected=False)
+        bpy.ops.object.mode_set(mode="OBJECT")
+
+        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
+
+    if not ob:
+        bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=sphe_rad, location=loc)
+        ob = context.object
+        ob.name = ob.data.name = "PovSphere"
+        ob.pov.sphere_radius = sphe_rad
+        bpy.ops.object.mode_set(mode="EDIT")
+        bpy.ops.mesh.hide(unselected=False)
+        bpy.ops.object.mode_set(mode="OBJECT")
+        ob.data.use_auto_smooth = True
+        bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "SPHERE"
+
+
+class POV_OT_sphere_add(Operator):
+    """Add the representation of POV sphere using pov_sphere_define() function.
+
+    Use imported_loc when this operator is run by POV importer."""
+
+    bl_idname = "pov.addsphere"
+    bl_label = "Sphere"
+    bl_description = "Add Sphere Shape"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    # Keep in sync within model_properties.py section Sphere
+    # as this allows interactive update
+    sphe_rad: FloatProperty(name="Sphere radius", min=0.00, max=10.0, default=0.5)
+
+    imported_loc: FloatVectorProperty(
+        name="Imported Pov location", precision=6, default=(0.0, 0.0, 0.0)
+    )
+
+    def execute(self, context):
+        props = self.properties
+        sphe_rad = props.sphe_rad
+        if ob := context.object:
+            if ob.pov.imported_loc:
+                LOC = ob.pov.imported_loc
+        elif not props.imported_loc:
+            LOC = bpy.context.scene.cursor.location
+
+        else:
+            LOC = props.imported_loc
+            self.report(
+                {"INFO"},
+                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
+            )
+        pov_sphere_define(context, self, None, LOC)
+
+        return {"FINISHED"}
+
+    # def execute(self,context):
+    #  layers = 20*[False]
+    #  layers[0] = True
+
+    # bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=ob.pov.sphere_radius)
+    # ob = context.object
+    # bpy.ops.object.mode_set(mode="EDIT")
+    # self.report({'INFO'}, "This native POV-Ray primitive "
+    # "won't have any vertex to show in edit mode")
+    # bpy.ops.mesh.hide(unselected=False)
+    # bpy.ops.object.mode_set(mode="OBJECT")
+    # bpy.ops.object.shade_smooth()
+    # ob.pov.object_as = "SPHERE"
+    # ob.name = ob.data.name = 'PovSphere'
+    # return {'FINISHED'}
+
+
+class POV_OT_sphere_update(Operator):
+    """Update the POV sphere.
+
+    Delete its previous proxy geometry and rerun pov_sphere_define() function
+    with the new parameters"""
+
+    bl_idname = "pov.sphere_update"
+    bl_label = "Update"
+    bl_description = "Update Sphere"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    @classmethod
+    def poll(cls, context):
+        engine = context.scene.render.engine
+        ob = context.object
+        return (
+            ob
+            and ob.data
+            and ob.type == "MESH"
+            and ob.pov.object_as == "SPHERE"
+            and engine in cls.COMPAT_ENGINES
+        )
+    def execute(self, context):
+
+        pov_sphere_define(context, None, context.object, context.object.location)
+
+        return {"FINISHED"}
+
+
+# ----------------------------------- CONE ---------------------------------- #
+def pov_cone_define(context, op, ob):
+    """Add the representation of POV cone using pov_define_mesh() function.
+
+    Blender cone does not offer the same features such as a second radius."""
+    verts = []
+    faces = []
+    if op:
+        mesh = None
+        base = op.base
+        cap = op.cap
+        seg = op.seg
+        height = op.height
+    else:
+        assert ob
+        mesh = ob.data
+        base = ob.pov.cone_base_radius
+        cap = ob.pov.cone_cap_radius
+        seg = ob.pov.cone_segments
+        height = ob.pov.cone_height
+
+    zc = height / 2
+    zb = -zc
+    angle = 2 * pi / seg
+    t = 0
+    for i in range(seg):
+        xb = base * cos(t)
+        yb = base * sin(t)
+        xc = cap * cos(t)
+        yc = cap * sin(t)
+        verts.extend([(xb, yb, zb), (xc, yc, zc)])
+        t += angle
+    for i in range(seg):
+        f = i * 2
+        if i == seg - 1:
+            faces.append([0, 1, f + 1, f])
+        else:
+            faces.append([f + 2, f + 3, f + 1, f])
+    if base != 0:
+        base_face = [i * 2 for i in range(seg - 1, -1, -1)]
+        faces.append(base_face)
+    if cap != 0:
+        cap_face = [i * 2 + 1 for i in range(seg)]
+        faces.append(cap_face)
+
+    mesh = pov_define_mesh(mesh, verts, [], faces, "PovCone", True)
+    if not ob:
+        ob = object_data_add(context, mesh, operator=None)
+        ob.pov.cone_base_radius = base
+        ob.pov.cone_cap_radius = cap
+        ob.pov.cone_height = height
+        ob.pov.cone_base_z = zb
+        ob.pov.cone_cap_z = zc
+        ob.data.use_auto_smooth = True
+        bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "CONE"
+
+class POV_OT_cone_add(Operator):
+    """Add the representation of POV cone using pov_cone_define() function."""
+
+    bl_idname = "pov.addcone"
+    bl_label = "Cone"
+    bl_description = "Add Cone"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    # Keep in sync within model_properties.py section Cone
+    #     If someone knows how to define operators' props from a func, I'd be delighted to learn it!
+    base: FloatProperty(
+        name="Base radius",
+        description="The first radius of the cone",
+        default=1.0,
+        min=0.01,
+        max=100.0,
+    )
+    cap: FloatProperty(
+        name="Cap radius",
+        description="The second radius of the cone",
+        default=0.3,
+        min=0.0,
+        max=100.0,
+    )
+    seg: IntProperty(
+        name="Segments",
+        description="Radial segmentation of the proxy mesh",
+        default=16,
+        min=3,
+        max=265,
+    )
+    height: FloatProperty(
+        name="Height", description="Height of the cone", default=2.0, min=0.01, max=100.0
+    )
+
+    @classmethod
+    def poll(cls, context):
+        engine = context.scene.render.engine
+        return engine in cls.COMPAT_ENGINES
+
+    def execute(self, context):
+        pov_cone_define(context, self, None)
+
+        self.report(
+            {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
+        )
+        return {"FINISHED"}
+
+
+class POV_OT_cone_update(Operator):
+    """Update the POV cone.
+
+    Delete its previous proxy geometry and rerun pov_cone_define() function
+    with the new parameters"""
+
+    bl_idname = "pov.cone_update"
+    bl_label = "Update"
+    bl_description = "Update Cone"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    @classmethod
+    def poll(cls, context):
+        engine = context.scene.render.engine
+        ob = context.object
+
+        return (
+            ob
+            and ob.data
+            and ob.type == "MESH"
+            and ob.pov.object_as == "CONE"
+            and engine in cls.COMPAT_ENGINES
+        )
+    def execute(self, context):
+        bpy.ops.object.mode_set(mode="EDIT")
+        bpy.ops.mesh.reveal()
+        bpy.ops.mesh.select_all(action="SELECT")
+        bpy.ops.mesh.delete(type="VERT")
+        bpy.ops.object.mode_set(mode="OBJECT")
+
+        pov_cone_define(context, None, context.object)
+
+        return {"FINISHED"}
+
+
+class POV_OT_rainbow_add(Operator):
+    """Add the representation of POV rainbow using a Blender spot light.
+
+    Rainbows indeed propagate along a visibility cone.
+    Flag its primitive type with a specific ob.pov.object_as attribute
+    and leave access to edit mode to keep user editable handles.
+    Add a constraint to orient it towards camera because POV Rainbows
+    are view dependant and having it always initially visible is less
+    confusing"""
+
+    bl_idname = "pov.addrainbow"
+    bl_label = "Rainbow"
+    bl_description = "Add Rainbow"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    def execute(self, context):
+        cam = context.scene.camera
+        bpy.ops.object.light_add(type="SPOT", radius=1)
+        ob = context.object
+        ob.data.show_cone = False
+        ob.data.spot_blend = 0.5
+        # ob.data.shadow_buffer_clip_end = 0 # deprecated in 2.8
+        ob.data.shadow_buffer_clip_start = 4 * cam.location.length
+        ob.data.distance = cam.location.length
+        ob.data.energy = 0
+        ob.name = ob.data.name = "PovRainbow"
+        ob.pov.object_as = "RAINBOW"
+
+        # obj = context.object
+        bpy.ops.object.constraint_add(type="DAMPED_TRACK")
+
+        ob.constraints["Damped Track"].target = cam
+        ob.constraints["Damped Track"].track_axis = "TRACK_NEGATIVE_Z"
+        ob.location = -cam.location
+
+        # refocus on the actual rainbow
+        bpy.context.view_layer.objects.active = ob
+        ob.select_set(True)
+
+        return {"FINISHED"}
+
+
+# ----------------------------------- TORUS ----------------------------------- #
+def pov_torus_define(context, op, ob):
+    """Add the representation of POV torus using just a Blender torus.
+
+    Picking properties either from creation operator, import, or data update.
+    But flag its primitive type with a specific pov.object_as attribute and lock edit mode
+    to keep proxy consistency by hiding edit geometry."""
+
+    if op:
+        mas = op.mas
+        mis = op.mis
+        mar = op.mar
+        mir = op.mir
+    else:
+        assert ob
+        mas = ob.pov.torus_major_segments
+        mis = ob.pov.torus_minor_segments
+        mar = ob.pov.torus_major_radius
+        mir = ob.pov.torus_minor_radius
+
+        # keep object rotation and location for the add object operator
+        obrot = ob.rotation_euler
+        obloc = ob.location
+
+        bpy.ops.object.mode_set(mode="EDIT")
+        bpy.ops.mesh.reveal()
+        bpy.ops.mesh.select_all(action="SELECT")
+        bpy.ops.mesh.delete(type="VERT")
+        bpy.ops.mesh.primitive_torus_add(
+            rotation=obrot,
+            location=obloc,
+            major_segments=mas,
+            minor_segments=mis,
+            major_radius=mar,
+            minor_radius=mir,
+        )
+
+        bpy.ops.mesh.hide(unselected=False)
+        bpy.ops.object.mode_set(mode="OBJECT")
+
+    if not ob:
+        bpy.ops.mesh.primitive_torus_add(
+            major_segments=mas, minor_segments=mis, major_radius=mar, minor_radius=mir
+        )
+        ob = context.object
+        ob.name = ob.data.name = "PovTorus"
+        ob.pov.torus_major_segments = mas
+        ob.pov.torus_minor_segments = mis
+        ob.pov.torus_major_radius = mar
+        ob.pov.torus_minor_radius = mir
+        bpy.ops.object.mode_set(mode="EDIT")
+        bpy.ops.mesh.hide(unselected=False)
+        bpy.ops.object.mode_set(mode="OBJECT")
+        ob.data.use_auto_smooth = True
+        ob.data.auto_smooth_angle = 0.6
+        bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "TORUS"
+
+class POV_OT_torus_add(Operator):
+    """Add the representation of POV torus using using pov_torus_define() function."""
+
+    bl_idname = "pov.addtorus"
+    bl_label = "Torus"
+    bl_description = "Add Torus"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    # Keep in sync within model_properties.py section Torus
+    # as this allows interactive update
+    mas: IntProperty(name="Major Segments", description="", default=48, min=3, max=720)
+    mis: IntProperty(name="Minor Segments", description="", default=12, min=3, max=720)
+    mar: FloatProperty(name="Major Radius", description="", default=1.0)
+    mir: FloatProperty(name="Minor Radius", description="", default=0.25)
+
+    def execute(self, context):
+        props = self.properties
+        mar = props.mar
+        mir = props.mir
+        mas = props.mas
+        mis = props.mis
+        pov_torus_define(context, self, None)
+        self.report(
+            {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
+        )
+        return {"FINISHED"}
+
+
+class POV_OT_torus_update(Operator):
+    """Update the POV torus.
+
+    Delete its previous proxy geometry and rerun pov_torus_define() function
+    with the new parameters"""
+
+    bl_idname = "pov.torus_update"
+    bl_label = "Update"
+    bl_description = "Update Torus"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    @classmethod
+    def poll(cls, context):
+        engine = context.scene.render.engine
+        ob = context.object
+        return (
+            ob
+            and ob.data
+            and ob.type == "MESH"
+            and ob.pov.object_as == "TORUS"
+            and engine in cls.COMPAT_ENGINES
+        )
+
+    def execute(self, context):
+
+        pov_torus_define(context, None, context.object)
+
+        return {"FINISHED"}
+
+
+# -----------------------------------------------------------------------------
+
+
+class POV_OT_prism_add(Operator):
+    """Add the representation of POV prism using using an extruded curve."""
+
+    bl_idname = "pov.addprism"
+    bl_label = "Prism"
+    bl_description = "Create Prism"
+    bl_options = {'REGISTER', 'UNDO'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    prism_n: IntProperty(name="Sides", description="Number of sides", default=5, min=3, max=720)
+    prism_r: FloatProperty(name="Radius", description="Radius", default=1.0)
+
+    def execute(self, context):
+
+        props = self.properties
+        loft_data = bpy.data.curves.new("Prism", type="CURVE")
+        loft_data.dimensions = "2D"
+        loft_data.resolution_u = 2
+        # loft_data.show_normal_face = False
+        loft_data.extrude = 2
+        n = props.prism_n
+        r = props.prism_r
+        coords = []
+        z = 0
+        angle = 0
+        for p in range(n):
+            x = r * cos(angle)
+            y = r * sin(angle)
+            coords.append((x, y, z))
+            angle += pi * 2 / n
+        poly = loft_data.splines.new("POLY")
+        poly.points.add(len(coords) - 1)
+        for i, coord in enumerate(coords):
+            x, y, z = coord
+            poly.points[i].co = (x, y, z, 1)
+        poly.use_cyclic_u = True
+
+        ob = bpy.data.objects.new("Prism_shape", loft_data)
+        scn = bpy.context.scene
+        scn.collection.objects.link(ob)
+        context.view_layer.objects.active = ob
+        ob.select_set(True)
+        bpy.ops.object.mode_set(mode="OBJECT")
+        bpy.ops.object.shade_flat()
+        ob.data.fill_mode = 'BOTH'
+        ob.pov.curveshape = "prism"
+        ob.name = ob.data.name = "Prism"
+        return {"FINISHED"}
+
+
+classes = (
+    POV_OT_plane_add,
+    POV_OT_box_add,
+    POV_OT_cylinder_add,
+    POV_OT_cylinder_update,
+    POV_OT_sphere_add,
+    POV_OT_sphere_update,
+    POV_OT_cone_add,
+    POV_OT_cone_update,
+    POV_OT_rainbow_add,
+    POV_OT_torus_add,
+    POV_OT_torus_update,
+    POV_OT_prism_add,
+)
+
+
+def register():
+    for cls in classes:
+        register_class(cls)
+
+
+def unregister():
+    for cls in reversed(classes):
+        unregister_class(cls)
diff --git a/render_povray/object_primitives.py b/render_povray/model_primitives_topology.py
old mode 100755
new mode 100644
similarity index 51%
rename from render_povray/object_primitives.py
rename to render_povray/model_primitives_topology.py
index d017c4d07..6204ee04c
--- a/render_povray/object_primitives.py
+++ b/render_povray/model_primitives_topology.py
@@ -3,6 +3,7 @@
 # <pep8 compliant>
 
 """ Get POV-Ray specific objects In and Out of Blender """
+
 from math import pi, cos, sin
 import os.path
 import bpy
@@ -22,88 +23,17 @@ from bpy.props import (
 
 from mathutils import Vector, Matrix
 
+from . import model_primitives
 
-# import collections
-
-
-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 .object_mesh_topology.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 pov_define_mesh(mesh, verts, edges, faces, name, hide_geometry=True):
-    """Generate proxy mesh."""
-    if mesh is None:
-        mesh = bpy.data.meshes.new(name)
-    mesh.from_pydata(verts, edges, faces)
-    mesh.update()
-    mesh.validate(
-        verbose=False
-    )  # Set it to True to see debug messages (helps ensure you generate valid geometry).
-    if hide_geometry:
-        mesh.vertices.foreach_set("hide", [True] * len(mesh.vertices))
-        mesh.edges.foreach_set("hide", [True] * len(mesh.edges))
-        mesh.polygons.foreach_set("hide", [True] * len(mesh.polygons))
-    return mesh
-
-
-class POVRAY_OT_lathe_add(Operator):
+class POV_OT_lathe_add(Operator):
     """Add the representation of POV lathe using a screw modifier."""
 
     bl_idname = "pov.addlathe"
     bl_label = "Lathe"
     bl_description = "adds lathe"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
     def execute(self, context):
         # ayers=[False]*20
         # layers[0]=True
@@ -115,18 +45,18 @@ class POVRAY_OT_lathe_add(Operator):
         ob = context.view_layer.objects.active
         ob_data = ob.data
         ob.name = ob_data.name = "PovLathe"
-        ob_data.dimensions = '2D'
-        ob_data.transform(Matrix.Rotation(-pi / 2.0, 4, 'Z'))
-        ob.pov.object_as = 'LATHE'
+        ob_data.dimensions = "2D"
+        ob_data.transform(Matrix.Rotation(-pi / 2.0, 4, "Z"))
+        ob.pov.object_as = "LATHE"
         self.report(
-            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
+            {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
         )
         ob.pov.curveshape = "lathe"
-        bpy.ops.object.modifier_add(type='SCREW')
+        bpy.ops.object.modifier_add(type="SCREW")
         mod = ob.modifiers[-1]
-        mod.axis = 'Y'
+        mod.axis = "Y"
         mod.show_render = False
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 def pov_superellipsoid_define(context, op, ob):
@@ -170,14 +100,10 @@ def pov_superellipsoid_define(context, op, ob):
             step += 1
             angSegment += stepSegment
             x = r * (abs(cos(angRing)) ** n1) * (abs(cos(angSegment)) ** n2)
-            if (cos(angRing) < 0 < cos(angSegment)) or (
-                    cos(angRing) > 0 > cos(angSegment)
-            ):
+            if (cos(angRing) < 0 < cos(angSegment)) or (cos(angRing) > 0 > cos(angSegment)):
                 x = -x
             y = r * (abs(cos(angRing)) ** n1) * (abs(sin(angSegment)) ** n2)
-            if (cos(angRing) < 0 < sin(angSegment)) or (
-                    cos(angRing) > 0 > sin(angSegment)
-            ):
+            if (cos(angRing) < 0 < sin(angSegment)) or (cos(angRing) > 0 > sin(angSegment)):
                 y = -y
             z = r * (abs(sin(angRing)) ** n1)
             if sin(angRing) < 0:
@@ -186,9 +112,8 @@ def pov_superellipsoid_define(context, op, ob):
             y = round(y, 4)
             z = round(z, 4)
             verts.append((x, y, z))
-    if edit == 'TRIANGLES':
-        verts.append((0, 0, 1))
-        verts.append((0, 0, -1))
+    if edit == "TRIANGLES":
+        verts.extend([(0, 0, 1),(0, 0, -1)])
 
     faces = []
 
@@ -200,7 +125,7 @@ def pov_superellipsoid_define(context, op, ob):
             if p == v - 1:
                 face = (m + p, m, v + m, v + m + p)
             faces.append(face)
-    if edit == 'TRIANGLES':
+    if edit == "TRIANGLES":
         indexUp = len(verts) - 2
         indexDown = len(verts) - 1
         indexStartDown = len(verts) - 2 - v
@@ -218,24 +143,21 @@ def pov_superellipsoid_define(context, op, ob):
             if i == v - 1:
                 face = (indexUp, i + indexStartDown, indexStartDown)
                 faces.append(face)
-    if edit == 'NGONS':
-        face = []
-        for i in range(0, v):
-            face.append(i)
+    if edit == "NGONS":
+        face = list(range(v))
         faces.append(face)
         face = []
         indexUp = len(verts) - 1
         for i in range(0, v):
             face.append(indexUp - i)
         faces.append(face)
-    mesh = pov_define_mesh(mesh, verts, [], faces, "SuperEllipsoid")
+    mesh = model_primitives.pov_define_mesh(mesh, verts, [], faces, "SuperEllipsoid")
 
     if not ob:
         ob = object_data_add(context, mesh, operator=None)
         # engine = context.scene.render.engine what for?
         ob = context.object
         ob.name = ob.data.name = "PovSuperellipsoid"
-        ob.pov.object_as = 'SUPERELLIPSOID'
         ob.pov.se_param1 = n2
         ob.pov.se_param2 = n1
 
@@ -248,18 +170,20 @@ def pov_superellipsoid_define(context, op, ob):
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.hide(unselected=False)
         bpy.ops.object.mode_set(mode="OBJECT")
+        ob.data.auto_smooth_angle = 1.3
+        bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "SUPERELLIPSOID"
 
-
-class POVRAY_OT_superellipsoid_add(Operator):
+class POV_OT_superellipsoid_add(Operator):
     """Add the representation of POV superellipsoid using the pov_superellipsoid_define()."""
 
     bl_idname = "pov.addsuperellipsoid"
     bl_label = "Add SuperEllipsoid"
     bl_description = "Create a SuperEllipsoid"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
-    # Keep in sync within object_properties.py section Superellipsoid
+    # Keep in sync within model_properties.py section Superellipsoid
     # as this allows interactive update
     #     If someone knows how to define operators' props from a func, I'd be delighted to learn it!
     # XXX ARE the first two used for import ? could we hide or suppress them otherwise?
@@ -291,7 +215,7 @@ class POVRAY_OT_superellipsoid_add(Operator):
         items=[("NOTHING", "Nothing", ""), ("NGONS", "N-Gons", ""), ("TRIANGLES", "Triangles", "")],
         name="Fill up and down",
         description="",
-        default='TRIANGLES',
+        default="TRIANGLES",
     )
 
     @classmethod
@@ -303,13 +227,13 @@ class POVRAY_OT_superellipsoid_add(Operator):
         pov_superellipsoid_define(context, self, None)
 
         self.report(
-            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
+            {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
         )
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_superellipsoid_update(Operator):
+class POV_OT_superellipsoid_update(Operator):
     """Update the superellipsoid.
 
     Delete its previous proxy geometry and rerun pov_superellipsoid_define() function
@@ -319,24 +243,24 @@ class POVRAY_OT_superellipsoid_update(Operator):
     bl_label = "Update"
     bl_description = "Update Superellipsoid"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         ob = context.object
-        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
+        return ob and ob.data and ob.type == "MESH" and engine in cls.COMPAT_ENGINES
 
     def execute(self, context):
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.reveal()
-        bpy.ops.mesh.select_all(action='SELECT')
-        bpy.ops.mesh.delete(type='VERT')
+        bpy.ops.mesh.select_all(action="SELECT")
+        bpy.ops.mesh.delete(type="VERT")
         bpy.ops.object.mode_set(mode="OBJECT")
 
         pov_superellipsoid_define(context, None, context.object)
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 def create_faces(vert_idx_1, vert_idx_2, closed=False, flipped=False):
@@ -358,36 +282,30 @@ def create_faces(vert_idx_1, vert_idx_2, closed=False, flipped=False):
             face = [vert_idx_1[0], vert_idx_2[0], vert_idx_2[total - 1]]
             if not fan:
                 face.append(vert_idx_1[total - 1])
-            faces.append(face)
-
         else:
             face = [vert_idx_2[0], vert_idx_1[0]]
             if not fan:
                 face.append(vert_idx_1[total - 1])
             face.append(vert_idx_2[total - 1])
-            faces.append(face)
+        faces.append(face)
+
     for num in range(total - 1):
         if flipped:
             if fan:
                 face = [vert_idx_2[num], vert_idx_1[0], vert_idx_2[num + 1]]
             else:
                 face = [vert_idx_2[num], vert_idx_1[num], vert_idx_1[num + 1], vert_idx_2[num + 1]]
-            faces.append(face)
+        elif fan:
+            face = [vert_idx_1[0], vert_idx_2[num], vert_idx_2[num + 1]]
         else:
-            if fan:
-                face = [vert_idx_1[0], vert_idx_2[num], vert_idx_2[num + 1]]
-            else:
-                face = [vert_idx_1[num], vert_idx_2[num], vert_idx_2[num + 1], vert_idx_1[num + 1]]
-            faces.append(face)
-
+            face = [vert_idx_1[num], vert_idx_2[num], vert_idx_2[num + 1], vert_idx_1[num + 1]]
+        faces.append(face)
     return faces
 
 
 def power(a, b):
     """Workaround to negative a, where the math.pow() method would return a ValueError."""
-    if a < 0:
-        return -((-a) ** b)
-    return a ** b
+    return -((-a) ** b) if a < 0 else a**b
 
 
 def supertoroid(R, r, u, v, n1, n2):
@@ -446,10 +364,10 @@ def pov_supertorus_define(context, op, ob):
         if rad2 > rad1:
             rad1 = rad2
     verts, faces = supertoroid(rad1, rad2, st_u, st_v, st_n1, st_n2)
-    mesh = pov_define_mesh(mesh, verts, [], faces, "PovSuperTorus", True)
+    mesh = model_primitives.pov_define_mesh(mesh, verts, [], faces, "PovSuperTorus", True)
     if not ob:
         ob = object_data_add(context, mesh, operator=None)
-        ob.pov.object_as = 'SUPERTORUS'
+        ob.pov.object_as = "SUPERTORUS"
         ob.pov.st_major_radius = st_R
         ob.pov.st_minor_radius = st_r
         ob.pov.st_u = st_u
@@ -460,14 +378,14 @@ def pov_supertorus_define(context, op, ob):
         ob.pov.st_edit = st_edit
 
 
-class POVRAY_OT_supertorus_add(Operator):
+class POV_OT_supertorus_add(Operator):
     """Add the representation of POV supertorus using the pov_supertorus_define() function."""
 
     bl_idname = "pov.addsupertorus"
     bl_label = "Add Supertorus"
     bl_description = "Create a SuperTorus"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     st_R: FloatProperty(
         name="big radius",
@@ -502,7 +420,7 @@ class POVRAY_OT_supertorus_add(Operator):
     st_ie: BoolProperty(
         name="Use Int.+Ext. radii", description="Use internal and external radii", default=False
     )
-    st_edit: BoolProperty(name="", description="", default=False, options={'HIDDEN'})
+    st_edit: BoolProperty(name="", description="", default=False, options={"HIDDEN"})
 
     @classmethod
     def poll(cls, context):
@@ -513,12 +431,12 @@ class POVRAY_OT_supertorus_add(Operator):
         pov_supertorus_define(context, self, None)
 
         self.report(
-            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
+            {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
         )
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_supertorus_update(Operator):
+class POV_OT_supertorus_update(Operator):
     """Update the supertorus.
 
     Delete its previous proxy geometry and rerun pov_supetorus_define() function
@@ -528,35 +446,35 @@ class POVRAY_OT_supertorus_update(Operator):
     bl_label = "Update"
     bl_description = "Update SuperTorus"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         ob = context.object
-        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
+        return ob and ob.data and ob.type == "MESH" and engine in cls.COMPAT_ENGINES
 
     def execute(self, context):
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.reveal()
-        bpy.ops.mesh.select_all(action='SELECT')
-        bpy.ops.mesh.delete(type='VERT')
+        bpy.ops.mesh.select_all(action="SELECT")
+        bpy.ops.mesh.delete(type="VERT")
         bpy.ops.object.mode_set(mode="OBJECT")
 
         pov_supertorus_define(context, None, context.object)
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 # -----------------------------------------------------------------------------
-class POVRAY_OT_loft_add(Operator):
+class POV_OT_loft_add(Operator):
     """Create the representation of POV loft using Blender curves."""
 
     bl_idname = "pov.addloft"
     bl_label = "Add Loft Data"
     bl_description = "Create a Curve data for Meshmaker"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     loft_n: IntProperty(
         name="Segments", description="Vertical segments", default=16, min=3, max=720
@@ -584,8 +502,8 @@ class POVRAY_OT_loft_add(Operator):
     def execute(self, context):
 
         props = self.properties
-        loft_data = bpy.data.curves.new('Loft', type='CURVE')
-        loft_data.dimensions = '3D'
+        loft_data = bpy.data.curves.new("Loft", type="CURVE")
+        loft_data.dimensions = "3D"
         loft_data.resolution_u = 2
         # loft_data.show_normal_face = False # deprecated in 2.8
         n = props.loft_n
@@ -607,7 +525,7 @@ class POVRAY_OT_loft_add(Operator):
                 coords.append((x, y, z))
                 angle += pi * 2 / n
             r0 += distB
-            nurbs = loft_data.splines.new('NURBS')
+            nurbs = loft_data.splines.new("NURBS")
             nurbs.points.add(len(coords) - 1)
             for c, coord in enumerate(coords):
                 x, y, z = coord
@@ -622,7 +540,7 @@ class POVRAY_OT_loft_add(Operator):
                 y = r * sin(angle)
                 coords.append((x, y, z))
                 angle += pi * 2 / n
-            nurbs = loft_data.splines.new('NURBS')
+            nurbs = loft_data.splines.new("NURBS")
             nurbs.points.add(len(coords) - 1)
             for c, coord in enumerate(coords):
                 x, y, z = coord
@@ -637,7 +555,7 @@ class POVRAY_OT_loft_add(Operator):
                 y = r * sin(angle)
                 coords.append((x, y, z))
                 angle += pi * 2 / n
-            nurbs = loft_data.splines.new('NURBS')
+            nurbs = loft_data.splines.new("NURBS")
             nurbs.points.add(len(coords) - 1)
             for c, coord in enumerate(coords):
                 x, y, z = coord
@@ -655,471 +573,19 @@ class POVRAY_OT_loft_add(Operator):
                 coords.append((x, y, z))
                 angle += pi * 2 / n
             r -= distB
-            nurbs = loft_data.splines.new('NURBS')
+            nurbs = loft_data.splines.new("NURBS")
             nurbs.points.add(len(coords) - 1)
             for c, coord in enumerate(coords):
                 x, y, z = coord
                 nurbs.points[c].co = (x, y, z, 1)
             nurbs.use_cyclic_u = True
-        ob = bpy.data.objects.new('Loft_shape', loft_data)
+        ob = bpy.data.objects.new("Loft_shape", loft_data)
         scn = bpy.context.scene
         scn.collection.objects.link(ob)
         context.view_layer.objects.active = ob
         ob.select_set(True)
         ob.pov.curveshape = "loft"
-        return {'FINISHED'}
-
-
-class POVRAY_OT_plane_add(Operator):
-    """Add the representation of POV infinite plane using just a very big Blender Plane.
-
-    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
-    to keep proxy consistency by hiding edit geometry."""
-
-    bl_idname = "pov.addplane"
-    bl_label = "Plane"
-    bl_description = "Add Plane"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    def execute(self, context):
-        # layers = 20*[False]
-        # layers[0] = True
-        bpy.ops.mesh.primitive_plane_add(size=10000)
-        ob = context.object
-        ob.name = ob.data.name = 'PovInfinitePlane'
-        bpy.ops.object.mode_set(mode="EDIT")
-        self.report(
-            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
-        )
-        bpy.ops.mesh.hide(unselected=False)
-        bpy.ops.object.mode_set(mode="OBJECT")
-        bpy.ops.object.shade_smooth()
-        ob.pov.object_as = "PLANE"
-        return {'FINISHED'}
-
-
-class POVRAY_OT_box_add(Operator):
-    """Add the representation of POV box using a simple Blender mesh cube.
-
-    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
-    to keep proxy consistency by hiding edit geometry."""
-
-    bl_idname = "pov.addbox"
-    bl_label = "Box"
-    bl_description = "Add Box"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    def execute(self, context):
-        # layers = 20*[False]
-        # layers[0] = True
-        bpy.ops.mesh.primitive_cube_add()
-        ob = context.object
-        ob.name = ob.data.name = 'PovBox'
-        bpy.ops.object.mode_set(mode="EDIT")
-        self.report(
-            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
-        )
-        bpy.ops.mesh.hide(unselected=False)
-        bpy.ops.object.mode_set(mode="OBJECT")
-        ob.pov.object_as = "BOX"
-        return {'FINISHED'}
-
-
-def pov_cylinder_define(context, op, ob, radius, loc, loc_cap):
-    """Pick POV cylinder properties either from creation operator, import, or data update """
-    if op:
-        R = op.R
-        loc = bpy.context.scene.cursor.location
-        loc_cap[0] = loc[0]
-        loc_cap[1] = loc[1]
-        loc_cap[2] = loc[2] + 2
-    vec = Vector(loc_cap) - Vector(loc)
-    depth = vec.length
-    rot = Vector((0, 0, 1)).rotation_difference(vec)  # Rotation from Z axis.
-    trans = rot @ Vector(
-        (0, 0, depth / 2)
-    )  # Such that origin is at center of the base of the cylinder.
-    roteuler = rot.to_euler()
-    if not ob:
-        bpy.ops.object.add(type='MESH', location=loc)
-        ob = context.object
-        ob.name = ob.data.name = "PovCylinder"
-        ob.pov.cylinder_radius = radius
-        ob.pov.cylinder_location_cap = vec
-        ob.pov.object_as = "CYLINDER"
-    else:
-        ob.location = loc
-
-    bpy.ops.object.mode_set(mode="EDIT")
-    bpy.ops.mesh.reveal()
-    bpy.ops.mesh.select_all(action='SELECT')
-    bpy.ops.mesh.delete(type='VERT')
-    bpy.ops.mesh.primitive_cylinder_add(
-        radius=radius, depth=depth, location=loc, rotation=roteuler, end_fill_type='NGON'
-    )  # 'NOTHING'
-    bpy.ops.transform.translate(value=trans)
-
-    bpy.ops.mesh.hide(unselected=False)
-    bpy.ops.object.mode_set(mode="OBJECT")
-    bpy.ops.object.shade_smooth()
-
-
-class POVRAY_OT_cylinder_add(Operator):
-    """Add the representation of POV cylinder using pov_cylinder_define() function.
-
-    Use imported_cyl_loc when this operator is run by POV importer."""
-
-    bl_idname = "pov.addcylinder"
-    bl_label = "Cylinder"
-    bl_description = "Add Cylinder"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    # Keep in sync within object_properties.py section Cylinder
-    # as this allows interactive update
-    R: FloatProperty(name="Cylinder radius", min=0.00, max=10.0, default=1.0)
-
-    imported_cyl_loc: FloatVectorProperty(
-        name="Imported Pov base location", precision=6, default=(0.0, 0.0, 0.0)
-    )
-
-    imported_cyl_loc_cap: FloatVectorProperty(
-        name="Imported Pov cap location", precision=6, default=(0.0, 0.0, 2.0)
-    )
-
-    def execute(self, context):
-        props = self.properties
-        R = props.R
-        ob = context.object
-        # layers = 20*[False]
-        # layers[0] = True
-        if ob:
-            if ob.pov.imported_cyl_loc:
-                LOC = ob.pov.imported_cyl_loc
-            if ob.pov.imported_cyl_loc_cap:
-                LOC_CAP = ob.pov.imported_cyl_loc_cap
-        elif not props.imported_cyl_loc:
-            LOC_CAP = LOC = bpy.context.scene.cursor.location
-            LOC_CAP[2] += 2.0
-        else:
-            LOC = props.imported_cyl_loc
-            LOC_CAP = props.imported_cyl_loc_cap
-            self.report(
-                {'INFO'},
-                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
-            )
-
-        pov_cylinder_define(context, self, None, self.R, LOC, LOC_CAP)
-
-        return {'FINISHED'}
-
-
-class POVRAY_OT_cylinder_update(Operator):
-    """Update the POV cylinder.
-
-    Delete its previous proxy geometry and rerun pov_cylinder_define() function
-    with the new parameters"""
-
-    bl_idname = "pov.cylinder_update"
-    bl_label = "Update"
-    bl_description = "Update Cylinder"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    @classmethod
-    def poll(cls, context):
-        engine = context.scene.render.engine
-        ob = context.object
-        return (
-            ob
-            and ob.data
-            and ob.type == 'MESH'
-            and ob.pov.object_as == "CYLINDER"
-            and engine in cls.COMPAT_ENGINES
-        )
-
-    def execute(self, context):
-        ob = context.object
-        radius = ob.pov.cylinder_radius
-        loc = ob.location
-        loc_cap = loc + ob.pov.cylinder_location_cap
-
-        pov_cylinder_define(context, None, ob, radius, loc, loc_cap)
-
-        return {'FINISHED'}
-
-
-# ----------------------------------- SPHERE---------------------------------- #
-def pov_sphere_define(context, op, ob, loc):
-    """create the representation of POV sphere using a Blender icosphere.
-
-    Its nice platonic solid curvature better represents pov rendertime
-    tessellation than a UV sphere"""
-
-    if op:
-        R = op.R
-        loc = bpy.context.scene.cursor.location
-    else:
-        assert ob
-        R = ob.pov.sphere_radius
-
-        # keep object rotation and location for the add object operator
-        obrot = ob.rotation_euler
-        # obloc = ob.location
-        obscale = ob.scale
-
-        bpy.ops.object.mode_set(mode="EDIT")
-        bpy.ops.mesh.reveal()
-        bpy.ops.mesh.select_all(action='SELECT')
-        bpy.ops.mesh.delete(type='VERT')
-        bpy.ops.mesh.primitive_ico_sphere_add(
-            subdivisions=4, radius=ob.pov.sphere_radius, location=loc, rotation=obrot
-        )
-        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
-        bpy.ops.transform.resize(value=obscale)
-        # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
-
-        bpy.ops.mesh.hide(unselected=False)
-        bpy.ops.object.mode_set(mode="OBJECT")
-        bpy.ops.object.shade_smooth()
-        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
-
-    if not ob:
-        bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=R, location=loc)
-        ob = context.object
-        ob.name = ob.data.name = "PovSphere"
-        ob.pov.object_as = "SPHERE"
-        ob.pov.sphere_radius = R
-        bpy.ops.object.mode_set(mode="EDIT")
-        bpy.ops.mesh.hide(unselected=False)
-        bpy.ops.object.mode_set(mode="OBJECT")
-
-
-class POVRAY_OT_sphere_add(Operator):
-    """Add the representation of POV sphere using pov_sphere_define() function.
-
-    Use imported_loc when this operator is run by POV importer."""
-
-    bl_idname = "pov.addsphere"
-    bl_label = "Sphere"
-    bl_description = "Add Sphere Shape"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    # Keep in sync within object_properties.py section Sphere
-    # as this allows interactive update
-    R: FloatProperty(name="Sphere radius", min=0.00, max=10.0, default=0.5)
-
-    imported_loc: FloatVectorProperty(
-        name="Imported Pov location", precision=6, default=(0.0, 0.0, 0.0)
-    )
-
-    def execute(self, context):
-        props = self.properties
-        R = props.R
-        ob = context.object
-
-        if ob:
-            if ob.pov.imported_loc:
-                LOC = ob.pov.imported_loc
-        elif not props.imported_loc:
-            LOC = bpy.context.scene.cursor.location
-
-        else:
-            LOC = props.imported_loc
-            self.report(
-                {'INFO'},
-                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
-            )
-        pov_sphere_define(context, self, None, LOC)
-
-        return {'FINISHED'}
-
-    # def execute(self,context):
-    #  layers = 20*[False]
-    #  layers[0] = True
-
-    # bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=ob.pov.sphere_radius)
-    # ob = context.object
-    # bpy.ops.object.mode_set(mode="EDIT")
-    # self.report({'INFO'}, "This native POV-Ray primitive "
-    # "won't have any vertex to show in edit mode")
-    # bpy.ops.mesh.hide(unselected=False)
-    # bpy.ops.object.mode_set(mode="OBJECT")
-    # bpy.ops.object.shade_smooth()
-    # ob.pov.object_as = "SPHERE"
-    # ob.name = ob.data.name = 'PovSphere'
-    # return {'FINISHED'}
-
-
-class POVRAY_OT_sphere_update(Operator):
-    """Update the POV sphere.
-
-    Delete its previous proxy geometry and rerun pov_sphere_define() function
-    with the new parameters"""
-
-    bl_idname = "pov.sphere_update"
-    bl_label = "Update"
-    bl_description = "Update Sphere"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    @classmethod
-    def poll(cls, context):
-        engine = context.scene.render.engine
-        ob = context.object
-        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
-
-    def execute(self, context):
-
-        pov_sphere_define(context, None, context.object, context.object.location)
-
-        return {'FINISHED'}
-
-
-# ----------------------------------- CONE ---------------------------------- #
-def pov_cone_define(context, op, ob):
-    """Add the representation of POV cone using pov_define_mesh() function.
-
-    Blender cone does not offer the same features such as a second radius."""
-    verts = []
-    faces = []
-    if op:
-        mesh = None
-        base = op.base
-        cap = op.cap
-        seg = op.seg
-        height = op.height
-    else:
-        assert ob
-        mesh = ob.data
-        base = ob.pov.cone_base_radius
-        cap = ob.pov.cone_cap_radius
-        seg = ob.pov.cone_segments
-        height = ob.pov.cone_height
-
-    zc = height / 2
-    zb = -zc
-    angle = 2 * pi / seg
-    t = 0
-    for i in range(seg):
-        xb = base * cos(t)
-        yb = base * sin(t)
-        xc = cap * cos(t)
-        yc = cap * sin(t)
-        verts.append((xb, yb, zb))
-        verts.append((xc, yc, zc))
-        t += angle
-    for i in range(seg):
-        f = i * 2
-        if i == seg - 1:
-            faces.append([0, 1, f + 1, f])
-        else:
-            faces.append([f + 2, f + 3, f + 1, f])
-    if base != 0:
-        base_face = []
-        for i in range(seg - 1, -1, -1):
-            p = i * 2
-            base_face.append(p)
-        faces.append(base_face)
-    if cap != 0:
-        cap_face = []
-        for i in range(seg):
-            p = i * 2 + 1
-            cap_face.append(p)
-        faces.append(cap_face)
-
-    mesh = pov_define_mesh(mesh, verts, [], faces, "PovCone", True)
-    if not ob:
-        ob = object_data_add(context, mesh, operator=None)
-        ob.pov.object_as = "CONE"
-        ob.pov.cone_base_radius = base
-        ob.pov.cone_cap_radius = cap
-        ob.pov.cone_height = height
-        ob.pov.cone_base_z = zb
-        ob.pov.cone_cap_z = zc
-
-
-class POVRAY_OT_cone_add(Operator):
-    """Add the representation of POV cone using pov_cone_define() function."""
-
-    bl_idname = "pov.addcone"
-    bl_label = "Cone"
-    bl_description = "Add Cone"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    # Keep in sync within object_properties.py section Cone
-    #     If someone knows how to define operators' props from a func, I'd be delighted to learn it!
-    base: FloatProperty(
-        name="Base radius",
-        description="The first radius of the cone",
-        default=1.0,
-        min=0.01,
-        max=100.0,
-    )
-    cap: FloatProperty(
-        name="Cap radius",
-        description="The second radius of the cone",
-        default=0.3,
-        min=0.0,
-        max=100.0,
-    )
-    seg: IntProperty(
-        name="Segments",
-        description="Radial segmentation of the proxy mesh",
-        default=16,
-        min=3,
-        max=265,
-    )
-    height: FloatProperty(
-        name="Height", description="Height of the cone", default=2.0, min=0.01, max=100.0
-    )
-
-    @classmethod
-    def poll(cls, context):
-        engine = context.scene.render.engine
-        return engine in cls.COMPAT_ENGINES
-
-    def execute(self, context):
-        pov_cone_define(context, self, None)
-
-        self.report(
-            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
-        )
-        return {'FINISHED'}
-
-
-class POVRAY_OT_cone_update(Operator):
-    """Update the POV cone.
-
-    Delete its previous proxy geometry and rerun pov_cone_define() function
-    with the new parameters"""
-
-    bl_idname = "pov.cone_update"
-    bl_label = "Update"
-    bl_description = "Update Cone"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    @classmethod
-    def poll(cls, context):
-        engine = context.scene.render.engine
-        ob = context.object
-        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
-
-    def execute(self, context):
-        bpy.ops.object.mode_set(mode="EDIT")
-        bpy.ops.mesh.reveal()
-        bpy.ops.mesh.select_all(action='SELECT')
-        bpy.ops.mesh.delete(type='VERT')
-        bpy.ops.object.mode_set(mode="OBJECT")
-
-        pov_cone_define(context, None, context.object)
-
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 # ----------------------------------- ISOSURFACES ----------------------------------- #
@@ -1142,7 +608,7 @@ def pov_isosurface_view_define(context, op, ob, loc):
         # obloc = ob.location
         obscale = ob.scale
 
-        #bpy.ops.object.empty_add(type='CUBE', location=loc, rotation=obrot)
+        # bpy.ops.object.empty_add(type='CUBE', location=loc, rotation=obrot)
         bpy.ops.mesh.primitive_emptyvert_add()
 
         # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
@@ -1150,16 +616,17 @@ def pov_isosurface_view_define(context, op, ob, loc):
         # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
         bpy.ops.object.mode_set(mode="OBJECT")
     if not ob:
-        #bpy.ops.object.empty_add(type='CUBE', location=loc)
+        # bpy.ops.object.empty_add(type='CUBE', location=loc)
         bpy.ops.mesh.primitive_emptyvert_add()
         ob = context.object
         ob.name = ob.data.name = "PovIsosurface"
         ob.pov.object_as = "ISOSURFACE_VIEW"
         ob.pov.isosurface_eq = eq
-        ob.pov.contained_by = 'box'
+        ob.pov.contained_by = "box"
         bpy.ops.object.mode_set(mode="OBJECT")
 
-class POVRAY_OT_isosurface_add(Operator):
+
+class POV_OT_isosurface_add(Operator):
     """Add the representation of POV isosurface sphere by a Blender mesh icosphere.
 
     Flag its primitive type with a specific pov.object_as attribute and lock edit mode
@@ -1169,9 +636,9 @@ class POVRAY_OT_isosurface_add(Operator):
     bl_label = "Generic Isosurface"
     bl_description = "Add Isosurface"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
-    # Keep in sync within object_properties.py section Sphere
+    # Keep in sync within model_properties.py section Sphere
     # as this allows interactive update
     isosurface_eq: StringProperty(
         name="f(x,y,z)=",
@@ -1187,23 +654,24 @@ class POVRAY_OT_isosurface_add(Operator):
         # layers = 20*[False]
         # layers[0] = True
         props = self.properties
-        ob = context.object
-        if ob:
+        if ob := context.object:
             if ob.pov.imported_loc:
                 LOC = ob.pov.imported_loc
         elif not props.imported_loc:
             LOC = bpy.context.scene.cursor.location
         else:
             LOC = props.imported_loc
-        pov_isosurface_view_define(context, self, None, LOC)
-        self.report(
-            {'INFO'}, "This native POV-Ray primitive " "is only an abstract proxy in Blender"
-        )
-        return {'FINISHED'}
-
+        try:
+            pov_isosurface_view_define(context, self, None, LOC)
+            self.report(
+                {"INFO"}, "This native POV-Ray primitive " "is only an abstract proxy in Blender"
+            )
+        except AttributeError:
+            self.report({"INFO"}, "Please enable Add Mesh: Extra Objects addon")
+        return {"FINISHED"}
 
 
-class POVRAY_OT_isosurface_update(Operator):
+class POV_OT_isosurface_update(Operator):
     """Update the POV isosurface.
 
     Rerun pov_isosurface_view_define() function
@@ -1213,22 +681,22 @@ class POVRAY_OT_isosurface_update(Operator):
     bl_label = "Update"
     bl_description = "Update Isosurface"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         ob = context.object
-        return ob and ob.data and ob.type == 'ISOSURFACE_VIEW' and engine in cls.COMPAT_ENGINES
+        return ob and ob.data and ob.type == "ISOSURFACE_VIEW" and engine in cls.COMPAT_ENGINES
 
     def execute(self, context):
 
         pov_isosurface_view_define(context, None, context.object, context.object.location)
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_isosurface_box_add(Operator):
+class POV_OT_isosurface_box_add(Operator):
     """Add the representation of POV isosurface box using also just a Blender mesh cube.
 
     Flag its primitive type with a specific pov.object_as attribute and lock edit mode
@@ -1238,7 +706,7 @@ class POVRAY_OT_isosurface_box_add(Operator):
     bl_label = "Isosurface Box"
     bl_description = "Add Isosurface contained by Box"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
@@ -1247,17 +715,17 @@ class POVRAY_OT_isosurface_box_add(Operator):
         ob = context.object
         bpy.ops.object.mode_set(mode="EDIT")
         self.report(
-            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
+            {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
         )
         bpy.ops.mesh.hide(unselected=False)
         bpy.ops.object.mode_set(mode="OBJECT")
         ob.pov.object_as = "ISOSURFACE_NODE"
-        ob.pov.contained_by = 'box'
-        ob.name = 'PovIsosurfaceBox'
-        return {'FINISHED'}
+        ob.pov.contained_by = "box"
+        ob.name = "PovIsosurfaceBox"
+        return {"FINISHED"}
 
 
-class POVRAY_OT_isosurface_sphere_add(Operator):
+class POV_OT_isosurface_sphere_add(Operator):
     """Add the representation of POV isosurface sphere by a Blender mesh icosphere.
 
     Flag its primitive type with a specific pov.object_as attribute and lock edit mode
@@ -1267,7 +735,7 @@ class POVRAY_OT_isosurface_sphere_add(Operator):
     bl_label = "Isosurface Sphere"
     bl_description = "Add Isosurface contained by Sphere"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
@@ -1276,18 +744,18 @@ class POVRAY_OT_isosurface_sphere_add(Operator):
         ob = context.object
         bpy.ops.object.mode_set(mode="EDIT")
         self.report(
-            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
+            {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
         )
         bpy.ops.mesh.hide(unselected=False)
         bpy.ops.object.mode_set(mode="OBJECT")
         bpy.ops.object.shade_smooth()
         ob.pov.object_as = "ISOSURFACE_NODE"
-        ob.pov.contained_by = 'sphere'
-        ob.name = 'PovIsosurfaceSphere'
-        return {'FINISHED'}
+        ob.pov.contained_by = "sphere"
+        ob.name = "PovIsosurfaceSphere"
+        return {"FINISHED"}
 
 
-class POVRAY_OT_sphere_sweep_add(Operator):
+class POV_OT_sphere_sweep_add(Operator):
     """Add the representation of POV sphere_sweep using a Blender NURBS curve.
 
     Flag its primitive type with a specific ob.pov.curveshape attribute and
@@ -1297,7 +765,7 @@ class POVRAY_OT_sphere_sweep_add(Operator):
     bl_label = "Sphere Sweep"
     bl_description = "Create Sphere Sweep along curve"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
@@ -1308,13 +776,13 @@ class POVRAY_OT_sphere_sweep_add(Operator):
         ob.pov.curveshape = "sphere_sweep"
         ob.data.bevel_depth = 0.02
         ob.data.bevel_resolution = 4
-        ob.data.fill_mode = 'FULL'
+        ob.data.fill_mode = "FULL"
         # ob.data.splines[0].order_u = 4
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_blobsphere_add(Operator):
+class POV_OT_blobsphere_add(Operator):
     """Add the representation of POV blob using a Blender meta ball.
 
     No need to flag its primitive type as meta are exported to blobs
@@ -1324,18 +792,18 @@ class POVRAY_OT_blobsphere_add(Operator):
     bl_label = "Blob Sphere"
     bl_description = "Add Blob Sphere"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
         # layers[0] = True
-        bpy.ops.object.metaball_add(type='BALL')
+        bpy.ops.object.metaball_add(type="BALL")
         ob = context.object
         ob.name = "PovBlob"
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_blobcapsule_add(Operator):
+class POV_OT_blobcapsule_add(Operator):
     """Add the representation of POV blob using a Blender meta ball.
 
     No need to flag its primitive type as meta are exported to blobs
@@ -1345,18 +813,18 @@ class POVRAY_OT_blobcapsule_add(Operator):
     bl_label = "Blob Capsule"
     bl_description = "Add Blob Capsule"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
         # layers[0] = True
-        bpy.ops.object.metaball_add(type='CAPSULE')
+        bpy.ops.object.metaball_add(type="CAPSULE")
         ob = context.object
         ob.name = "PovBlob"
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_blobplane_add(Operator):
+class POV_OT_blobplane_add(Operator):
     """Add the representation of POV blob using a Blender meta ball.
 
     No need to flag its primitive type as meta are exported to blobs
@@ -1366,18 +834,18 @@ class POVRAY_OT_blobplane_add(Operator):
     bl_label = "Blob Plane"
     bl_description = "Add Blob Plane"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
         # layers[0] = True
-        bpy.ops.object.metaball_add(type='PLANE')
+        bpy.ops.object.metaball_add(type="PLANE")
         ob = context.object
         ob.name = "PovBlob"
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_blobellipsoid_add(Operator):
+class POV_OT_blobellipsoid_add(Operator):
     """Add the representation of POV blob using a Blender meta ball.
 
     No need to flag its primitive type as meta are exported to blobs
@@ -1386,18 +854,18 @@ class POVRAY_OT_blobellipsoid_add(Operator):
     bl_idname = "pov.addblobellipsoid"
     bl_label = "Blob Ellipsoid"
     bl_description = "Add Blob Ellipsoid"
-    bl_options = {'REGISTER', 'UNDO'}
+
 
     def execute(self, context):
         # layers = 20*[False]
         # layers[0] = True
-        bpy.ops.object.metaball_add(type='ELLIPSOID')
+        bpy.ops.object.metaball_add(type="ELLIPSOID")
         ob = context.object
         ob.name = "PovBlob"
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-class POVRAY_OT_blobcube_add(Operator):
+class POV_OT_blobcube_add(Operator):
     """Add the representation of POV blob using a Blender meta ball.
 
     No need to flag its primitive type as meta are exported to blobs
@@ -1407,61 +875,18 @@ class POVRAY_OT_blobcube_add(Operator):
     bl_label = "Blob Cube"
     bl_description = "Add Blob Cube"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         # layers = 20*[False]
         # layers[0] = True
-        bpy.ops.object.metaball_add(type='CUBE')
+        bpy.ops.object.metaball_add(type="CUBE")
         ob = context.object
         ob.name = "PovBlob"
-        return {'FINISHED'}
-
-
-class POVRAY_OT_rainbow_add(Operator):
-    """Add the representation of POV rainbow using a Blender spot light.
-
-    Rainbows indeed propagate along a visibility cone.
-    Flag its primitive type with a specific ob.pov.object_as attribute
-    and leave access to edit mode to keep user editable handles.
-    Add a constraint to orient it towards camera because POV Rainbows
-    are view dependant and having it always initially visible is less
-    confusing """
-
-    bl_idname = "pov.addrainbow"
-    bl_label = "Rainbow"
-    bl_description = "Add Rainbow"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    def execute(self, context):
-        cam = context.scene.camera
-        bpy.ops.object.light_add(type='SPOT', radius=1)
-        ob = context.object
-        ob.data.show_cone = False
-        ob.data.spot_blend = 0.5
-        # ob.data.shadow_buffer_clip_end = 0 # deprecated in 2.8
-        ob.data.shadow_buffer_clip_start = 4 * cam.location.length
-        ob.data.distance = cam.location.length
-        ob.data.energy = 0
-        ob.name = ob.data.name = "PovRainbow"
-        ob.pov.object_as = "RAINBOW"
-
-        # obj = context.object
-        bpy.ops.object.constraint_add(type='DAMPED_TRACK')
-
-        ob.constraints["Damped Track"].target = cam
-        ob.constraints["Damped Track"].track_axis = 'TRACK_NEGATIVE_Z'
-        ob.location = -cam.location
-
-        # refocus on the actual rainbow
-        bpy.context.view_layer.objects.active = ob
-        ob.select_set(True)
+        return {"FINISHED"}
 
-        return {'FINISHED'}
 
-
-class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
+class POV_OT_height_field_add(bpy.types.Operator, ImportHelper):
     """Add the representation of POV height_field using a displaced grid.
 
     texture slot fix and displace modifier will be needed because noise
@@ -1471,9 +896,9 @@ class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
     bl_label = "Height Field"
     bl_description = "Add Height Field"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
-    # Keep in sync within object_properties.py section HeightFields
+    # Keep in sync within model_properties.py section HeightFields
     # as this allows interactive update
 
     # filename_ext = ".png"
@@ -1503,9 +928,9 @@ class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
         img = bpy.data.images.load(impath)
         im_name = img.name
         im_name, file_extension = os.path.splitext(im_name)
-        hf_tex = bpy.data.textures.new('%s_hf_image' % im_name, type='IMAGE')
+        hf_tex = bpy.data.textures.new("%s_hf_image" % im_name, type="IMAGE")
         hf_tex.image = img
-        mat = bpy.data.materials.new('Tex_%s_hf' % im_name)
+        mat = bpy.data.materials.new("Tex_%s_hf" % im_name)
         hf_slot = mat.pov_texture_slots.add()
         hf_slot.texture = hf_tex.name
         # layers = 20*[False]
@@ -1517,7 +942,7 @@ class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
         h = int(h / res)
         bpy.ops.mesh.primitive_grid_add(x_subdivisions=w, y_subdivisions=h, size=0.5)
         ob = context.object
-        ob.name = ob.data.name = '%s' % im_name
+        ob.name = ob.data.name = "%s" % im_name
         ob.data.materials.append(mat)
         bpy.ops.object.mode_set(mode="EDIT")
         # bpy.ops.mesh.noise(factor=1) # TODO replace by displace modifier, noise deprecated in 2.8
@@ -1531,171 +956,11 @@ class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.hide(unselected=False)
         bpy.ops.object.mode_set(mode="OBJECT")
-        ob.pov.object_as = 'HEIGHT_FIELD'
+        ob.pov.object_as = "HEIGHT_FIELD"
         # POV-Ray will soon use only forwards slashes on every OS and already can
-        forward_impath = impath.replace(os.sep, '/')
+        forward_impath = impath.replace(os.sep, "/")
         ob.pov.hf_filename = forward_impath
-        return {'FINISHED'}
-
-
-# ----------------------------------- TORUS ----------------------------------- #
-def pov_torus_define(context, op, ob):
-    """Add the representation of POV torus using just a Blender torus.
-
-    Picking properties either from creation operator, import, or data update.
-    But flag its primitive type with a specific pov.object_as attribute and lock edit mode
-    to keep proxy consistency by hiding edit geometry."""
-
-    if op:
-        mas = op.mas
-        mis = op.mis
-        mar = op.mar
-        mir = op.mir
-    else:
-        assert ob
-        mas = ob.pov.torus_major_segments
-        mis = ob.pov.torus_minor_segments
-        mar = ob.pov.torus_major_radius
-        mir = ob.pov.torus_minor_radius
-
-        # keep object rotation and location for the add object operator
-        obrot = ob.rotation_euler
-        obloc = ob.location
-
-        bpy.ops.object.mode_set(mode="EDIT")
-        bpy.ops.mesh.reveal()
-        bpy.ops.mesh.select_all(action='SELECT')
-        bpy.ops.mesh.delete(type='VERT')
-        bpy.ops.mesh.primitive_torus_add(
-            rotation=obrot,
-            location=obloc,
-            major_segments=mas,
-            minor_segments=mis,
-            major_radius=mar,
-            minor_radius=mir,
-        )
-
-        bpy.ops.mesh.hide(unselected=False)
-        bpy.ops.object.mode_set(mode="OBJECT")
-
-    if not ob:
-        bpy.ops.mesh.primitive_torus_add(
-            major_segments=mas, minor_segments=mis, major_radius=mar, minor_radius=mir
-        )
-        ob = context.object
-        ob.name = ob.data.name = "PovTorus"
-        ob.pov.object_as = "TORUS"
-        ob.pov.torus_major_segments = mas
-        ob.pov.torus_minor_segments = mis
-        ob.pov.torus_major_radius = mar
-        ob.pov.torus_minor_radius = mir
-        bpy.ops.object.mode_set(mode="EDIT")
-        bpy.ops.mesh.hide(unselected=False)
-        bpy.ops.object.mode_set(mode="OBJECT")
-
-
-class POVRAY_OT_torus_add(Operator):
-    """Add the representation of POV torus using using pov_torus_define() function."""
-
-    bl_idname = "pov.addtorus"
-    bl_label = "Torus"
-    bl_description = "Add Torus"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    # Keep in sync within object_properties.py section Torus
-    # as this allows interactive update
-    mas: IntProperty(name="Major Segments", description="", default=48, min=3, max=720)
-    mis: IntProperty(name="Minor Segments", description="", default=12, min=3, max=720)
-    mar: FloatProperty(name="Major Radius", description="", default=1.0)
-    mir: FloatProperty(name="Minor Radius", description="", default=0.25)
-
-    def execute(self, context):
-        props = self.properties
-        mar = props.mar
-        mir = props.mir
-        mas = props.mas
-        mis = props.mis
-        pov_torus_define(context, self, None)
-        self.report(
-            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
-        )
-        return {'FINISHED'}
-
-
-class POVRAY_OT_torus_update(Operator):
-    """Update the POV torus.
-
-    Delete its previous proxy geometry and rerun pov_torus_define() function
-    with the new parameters"""
-
-    bl_idname = "pov.torus_update"
-    bl_label = "Update"
-    bl_description = "Update Torus"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    @classmethod
-    def poll(cls, context):
-        engine = context.scene.render.engine
-        ob = context.object
-        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
-
-    def execute(self, context):
-
-        pov_torus_define(context, None, context.object)
-
-        return {'FINISHED'}
-
-
-# -----------------------------------------------------------------------------
-
-
-class POVRAY_OT_prism_add(Operator):
-    """Add the representation of POV prism using using an extruded curve."""
-
-    bl_idname = "pov.addprism"
-    bl_label = "Prism"
-    bl_description = "Create Prism"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-
-    prism_n: IntProperty(name="Sides", description="Number of sides", default=5, min=3, max=720)
-    prism_r: FloatProperty(name="Radius", description="Radius", default=1.0)
-
-    def execute(self, context):
-
-        props = self.properties
-        loft_data = bpy.data.curves.new('Prism', type='CURVE')
-        loft_data.dimensions = '2D'
-        loft_data.resolution_u = 2
-        # loft_data.show_normal_face = False
-        loft_data.extrude = 2
-        n = props.prism_n
-        r = props.prism_r
-        coords = []
-        z = 0
-        angle = 0
-        for p in range(n):
-            x = r * cos(angle)
-            y = r * sin(angle)
-            coords.append((x, y, z))
-            angle += pi * 2 / n
-        poly = loft_data.splines.new('POLY')
-        poly.points.add(len(coords) - 1)
-        for i, coord in enumerate(coords):
-            x, y, z = coord
-            poly.points[i].co = (x, y, z, 1)
-        poly.use_cyclic_u = True
-
-        ob = bpy.data.objects.new('Prism_shape', loft_data)
-        scn = bpy.context.scene
-        scn.collection.objects.link(ob)
-        context.view_layer.objects.active = ob
-        ob.select_set(True)
-        ob.pov.curveshape = "prism"
-        ob.name = ob.data.name = "Prism"
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 # ----------------------------------- PARAMETRIC ----------------------------------- #
@@ -1733,8 +998,8 @@ def pov_parametric_define(context, op, ob):
 
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.reveal()
-        bpy.ops.mesh.select_all(action='SELECT')
-        bpy.ops.mesh.delete(type='VERT')
+        bpy.ops.mesh.select_all(action="SELECT")
+        bpy.ops.mesh.delete(type="VERT")
         bpy.ops.mesh.primitive_xyz_function_surface(
             x_eq=x_eq,
             y_eq=y_eq,
@@ -1744,7 +1009,7 @@ def pov_parametric_define(context, op, ob):
             range_v_min=v_min,
             range_v_max=v_max,
         )
-        bpy.ops.mesh.select_all(action='SELECT')
+        bpy.ops.mesh.select_all(action="SELECT")
         # extra work:
         bpy.ops.transform.translate(value=(obloc - curloc), proportional_size=1)
         # XXX TODO : https://devtalk.blender.org/t/bpy-ops-transform-rotate-option-axis/6235/7
@@ -1767,7 +1032,6 @@ def pov_parametric_define(context, op, ob):
         )
         ob = context.object
         ob.name = ob.data.name = "PovParametric"
-        ob.pov.object_as = "PARAMETRIC"
 
         ob.pov.u_min = u_min
         ob.pov.u_max = u_max
@@ -1780,18 +1044,20 @@ def pov_parametric_define(context, op, ob):
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.hide(unselected=False)
         bpy.ops.object.mode_set(mode="OBJECT")
+        ob.data.auto_smooth_angle = 0.6
+        bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "PARAMETRIC"
 
-
-class POVRAY_OT_parametric_add(Operator):
+class POV_OT_parametric_add(Operator):
     """Add the representation of POV parametric surfaces using pov_parametric_define() function."""
 
     bl_idname = "pov.addparametric"
     bl_label = "Parametric"
     bl_description = "Add Paramertic"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
-    # Keep in sync within object_properties.py section Parametric primitive
+    # Keep in sync within model_properties.py section Parametric primitive
     # as this allows interactive update
     u_min: FloatProperty(name="U Min", description="", default=0.0)
     v_min: FloatProperty(name="V Min", description="", default=0.0)
@@ -1813,16 +1079,15 @@ class POVRAY_OT_parametric_add(Operator):
         try:
             pov_parametric_define(context, self, None)
             self.report(
-                {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
+                {"INFO"},
+                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
             )
         except AttributeError:
-            self.report(
-                {'INFO'}, "Please enable Add Mesh: Extra Objects addon"
-            )
-        return {'FINISHED'}
+            self.report({"INFO"}, "Please enable Add Mesh: Extra Objects addon")
+        return {"FINISHED"}
 
 
-class POVRAY_OT_parametric_update(Operator):
+class POV_OT_parametric_update(Operator):
     """Update the representation of POV parametric surfaces.
 
     Delete its previous proxy geometry and rerun pov_parametric_define() function
@@ -1832,34 +1097,34 @@ class POVRAY_OT_parametric_update(Operator):
     bl_label = "Update"
     bl_description = "Update parametric object"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         ob = context.object
-        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
+        return ob and ob.data and ob.type == "MESH" and engine in cls.COMPAT_ENGINES
 
     def execute(self, context):
 
         pov_parametric_define(context, None, context.object)
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 # -----------------------------------------------------------------------------
 
 
-class POVRAY_OT_shape_polygon_to_circle_add(Operator):
+class POV_OT_polygon_to_circle_add(Operator):
     """Add the proxy mesh for POV Polygon to circle lofting macro"""
 
     bl_idname = "pov.addpolygontocircle"
     bl_label = "Polygon To Circle Blending"
     bl_description = "Add Polygon To Circle Blending Surface"
     bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
-    # Keep in sync within object_properties.py section PolygonToCircle properties
+    # Keep in sync within model_properties.py section PolygonToCircle properties
     # as this allows interactive update
     polytocircle_resolution: IntProperty(
         name="Resolution", description="", default=3, min=0, max=256
@@ -1877,72 +1142,62 @@ class POVRAY_OT_shape_polygon_to_circle_add(Operator):
         # layers = 20*[False]
         # layers[0] = True
         bpy.ops.mesh.primitive_circle_add(
-            vertices=ngon, radius=ngonR, fill_type='NGON', enter_editmode=True
+            vertices=ngon, radius=ngonR, fill_type="NGON", enter_editmode=True
         )
         bpy.ops.transform.translate(value=(0, 0, 1))
         bpy.ops.mesh.subdivide(number_cuts=resolution)
         numCircleVerts = ngon + (ngon * resolution)
-        bpy.ops.mesh.select_all(action='DESELECT')
+        bpy.ops.mesh.select_all(action="DESELECT")
         bpy.ops.mesh.primitive_circle_add(
-            vertices=numCircleVerts, radius=circleR, fill_type='NGON', enter_editmode=True
+            vertices=numCircleVerts, radius=circleR, fill_type="NGON", enter_editmode=True
         )
         bpy.ops.transform.translate(value=(0, 0, -1))
-        bpy.ops.mesh.select_all(action='SELECT')
+        bpy.ops.mesh.select_all(action="SELECT")
         bpy.ops.mesh.bridge_edge_loops()
         if ngon < 5:
-            bpy.ops.mesh.select_all(action='DESELECT')
+            bpy.ops.mesh.select_all(action="DESELECT")
             bpy.ops.mesh.primitive_circle_add(
-                vertices=ngon, radius=ngonR, fill_type='TRIFAN', enter_editmode=True
+                vertices=ngon, radius=ngonR, fill_type="TRIFAN", enter_editmode=True
             )
             bpy.ops.transform.translate(value=(0, 0, 1))
-            bpy.ops.mesh.select_all(action='SELECT')
+            bpy.ops.mesh.select_all(action="SELECT")
             bpy.ops.mesh.remove_doubles()
-        bpy.ops.object.mode_set(mode='OBJECT')
+        bpy.ops.object.mode_set(mode="OBJECT")
         ob = context.object
         ob.name = "Polygon_To_Circle"
-        ob.pov.object_as = 'POLYCIRCLE'
         ob.pov.ngon = ngon
         ob.pov.ngonR = ngonR
         ob.pov.circleR = circleR
         bpy.ops.object.mode_set(mode="EDIT")
         bpy.ops.mesh.hide(unselected=False)
         bpy.ops.object.mode_set(mode="OBJECT")
-        return {'FINISHED'}
+        #ob.data.auto_smooth_angle = 0.1
+        #bpy.ops.object.shade_smooth()
+        ob.pov.object_as = "POLYCIRCLE"
+        return {"FINISHED"}
 
 
 classes = (
-    POVRAY_OT_lathe_add,
-    POVRAY_OT_superellipsoid_add,
-    POVRAY_OT_superellipsoid_update,
-    POVRAY_OT_supertorus_add,
-    POVRAY_OT_supertorus_update,
-    POVRAY_OT_loft_add,
-    POVRAY_OT_plane_add,
-    POVRAY_OT_box_add,
-    POVRAY_OT_cylinder_add,
-    POVRAY_OT_cylinder_update,
-    POVRAY_OT_sphere_add,
-    POVRAY_OT_sphere_update,
-    POVRAY_OT_cone_add,
-    POVRAY_OT_cone_update,
-    POVRAY_OT_isosurface_add,
-    POVRAY_OT_isosurface_update,
-    POVRAY_OT_isosurface_box_add,
-    POVRAY_OT_isosurface_sphere_add,
-    POVRAY_OT_sphere_sweep_add,
-    POVRAY_OT_blobsphere_add,
-    POVRAY_OT_blobcapsule_add,
-    POVRAY_OT_blobplane_add,
-    POVRAY_OT_blobellipsoid_add,
-    POVRAY_OT_blobcube_add,
-    POVRAY_OT_rainbow_add,
-    POVRAY_OT_height_field_add,
-    POVRAY_OT_torus_add,
-    POVRAY_OT_torus_update,
-    POVRAY_OT_prism_add,
-    POVRAY_OT_parametric_add,
-    POVRAY_OT_parametric_update,
-    POVRAY_OT_shape_polygon_to_circle_add,
+    POV_OT_lathe_add,
+    POV_OT_superellipsoid_add,
+    POV_OT_superellipsoid_update,
+    POV_OT_supertorus_add,
+    POV_OT_supertorus_update,
+    POV_OT_loft_add,
+    POV_OT_isosurface_add,
+    POV_OT_isosurface_update,
+    POV_OT_isosurface_box_add,
+    POV_OT_isosurface_sphere_add,
+    POV_OT_sphere_sweep_add,
+    POV_OT_blobsphere_add,
+    POV_OT_blobcapsule_add,
+    POV_OT_blobplane_add,
+    POV_OT_blobellipsoid_add,
+    POV_OT_blobcube_add,
+    POV_OT_height_field_add,
+    POV_OT_parametric_add,
+    POV_OT_parametric_update,
+    POV_OT_polygon_to_circle_add,
 )
 
 
diff --git a/render_povray/object_properties.py b/render_povray/model_properties.py
old mode 100755
new mode 100644
similarity index 97%
rename from render_povray/object_properties.py
rename to render_povray/model_properties.py
index ed02f1d27..4a472b9be
--- a/render_povray/object_properties.py
+++ b/render_povray/model_properties.py
@@ -215,7 +215,8 @@ class RenderPovSettingsObject(PropertyGroup):
 
         """Update POV sphere primitive parameters at creation and anytime they change in UI."""
 
-        bpy.ops.pov.sphere_update()
+        if bpy.ops.pov.sphere_update.poll():
+            bpy.ops.pov.sphere_update()
 
     sphere_radius: FloatProperty(
         name="Sphere radius", min=0.00, max=10.0, default=0.5, update=prop_update_sphere
@@ -226,7 +227,8 @@ class RenderPovSettingsObject(PropertyGroup):
 
         """Update POV cone primitive parameters at creation and anytime they change in UI."""
 
-        bpy.ops.pov.cone_update()
+        if bpy.ops.pov.cone_update.poll():
+            bpy.ops.pov.cone_update()
 
     cone_base_radius: FloatProperty(
         name="Base radius",
@@ -279,7 +281,8 @@ class RenderPovSettingsObject(PropertyGroup):
 
         """Update POV parametric surface primitive settings at creation and on any UI change."""
 
-        bpy.ops.pov.parametric_update()
+        if bpy.ops.pov.parametric_update.poll():
+            bpy.ops.pov.parametric_update()
 
     u_min: FloatProperty(name="U Min", description="", default=0.0, update=prop_update_parametric)
 
@@ -307,7 +310,8 @@ class RenderPovSettingsObject(PropertyGroup):
 
         """Update POV torus primitive parameters at creation and anytime they change in UI."""
 
-        bpy.ops.pov.torus_update()
+        if bpy.ops.pov.torus_update.poll():
+            bpy.ops.pov.torus_update()
 
     torus_major_segments: IntProperty(
         name="Segments",
@@ -385,7 +389,8 @@ class RenderPovSettingsObject(PropertyGroup):
 
         """Update POV superellipsoid primitive settings at creation and on any UI change."""
 
-        bpy.ops.pov.superellipsoid_update()
+        if bpy.ops.pov.superellipsoid_update.poll():
+            bpy.ops.pov.superellipsoid_update()
 
     se_param1: FloatProperty(name="Parameter 1", description="", min=0.00, max=10.0, default=0.04)
 
@@ -455,7 +460,8 @@ class RenderPovSettingsObject(PropertyGroup):
 
         """Update POV supertorus primitive parameters not only at creation and on any UI change."""
 
-        bpy.ops.pov.supertorus_update()
+        if bpy.ops.pov.supertorus_update.poll():
+            bpy.ops.pov.supertorus_update()
 
     st_major_radius: FloatProperty(
         name="Major radius",
diff --git a/render_povray/nodes.py b/render_povray/nodes.py
new file mode 100644
index 000000000..d7662d142
--- /dev/null
+++ b/render_povray/nodes.py
@@ -0,0 +1,1056 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+""""Nodes based User interface for shaders exported to POV textures."""
+import bpy
+
+from bpy.utils import register_class, unregister_class
+from bpy.types import Node, CompositorNodeTree, TextureNodeTree
+from bpy.props import (
+    StringProperty,
+    BoolProperty,
+    IntProperty,
+    FloatProperty,
+    EnumProperty,
+)
+from . import nodes_properties
+
+
+# -------- Output
+class PovrayOutputNode(Node, nodes_properties.ObjectNodeTree):
+    """Output"""
+
+    bl_idname = "PovrayOutputNode"
+    bl_label = "Output"
+    bl_icon = "SHADING_TEXTURE"
+
+    def init(self, context):
+
+        self.inputs.new("PovraySocketTexture", "Texture")
+
+    def draw_buttons(self, context, layout):
+
+        ob = context.object
+        layout.prop(ob.pov, "object_ior", slider=True)
+
+    def draw_buttons_ext(self, context, layout):
+
+        ob = context.object
+        layout.prop(ob.pov, "object_ior", slider=True)
+
+    def draw_label(self):
+        return "Output"
+
+
+# -------- Material
+class PovrayTextureNode(Node, nodes_properties.ObjectNodeTree):
+    """Texture"""
+
+    bl_idname = "PovrayTextureNode"
+    bl_label = "Simple texture"
+    bl_icon = "SHADING_TEXTURE"
+
+    def init(self, context):
+
+        color = self.inputs.new("PovraySocketColor", "Pigment")
+        color.default_value = (1, 1, 1)
+        normal = self.inputs.new("NodeSocketFloat", "Normal")
+        normal.hide_value = True
+        finish = self.inputs.new("NodeSocketVector", "Finish")
+        finish.hide_value = True
+
+        self.outputs.new("PovraySocketTexture", "Texture")
+
+    def draw_label(self):
+        return "Simple texture"
+
+
+class PovrayFinishNode(Node, nodes_properties.ObjectNodeTree):
+    """Finish"""
+
+    bl_idname = "PovrayFinishNode"
+    bl_label = "Finish"
+    bl_icon = "SHADING_TEXTURE"
+
+    def init(self, context):
+
+        self.inputs.new("PovraySocketFloat_0_1", "Emission")
+        ambient = self.inputs.new("NodeSocketVector", "Ambient")
+        ambient.hide_value = True
+        diffuse = self.inputs.new("NodeSocketVector", "Diffuse")
+        diffuse.hide_value = True
+        specular = self.inputs.new("NodeSocketVector", "Highlight")
+        specular.hide_value = True
+        mirror = self.inputs.new("NodeSocketVector", "Mirror")
+        mirror.hide_value = True
+        iridescence = self.inputs.new("NodeSocketVector", "Iridescence")
+        iridescence.hide_value = True
+        subsurface = self.inputs.new("NodeSocketVector", "Translucency")
+        subsurface.hide_value = True
+        self.outputs.new("NodeSocketVector", "Finish")
+
+    def draw_label(self):
+        return "Finish"
+
+
+class PovrayDiffuseNode(Node, nodes_properties.ObjectNodeTree):
+    """Diffuse"""
+
+    bl_idname = "PovrayDiffuseNode"
+    bl_label = "Diffuse"
+    bl_icon = "MATSPHERE"
+
+    def init(self, context):
+
+        intensity = self.inputs.new("PovraySocketFloat_0_1", "Intensity")
+        intensity.default_value = 0.8
+        albedo = self.inputs.new("NodeSocketBool", "Albedo")
+        albedo.default_value = False
+        brilliance = self.inputs.new("PovraySocketFloat_0_10", "Brilliance")
+        brilliance.default_value = 1.8
+        self.inputs.new("PovraySocketFloat_0_1", "Crand")
+        self.outputs.new("NodeSocketVector", "Diffuse")
+
+    def draw_label(self):
+        return "Diffuse"
+
+
+class PovrayPhongNode(Node, nodes_properties.ObjectNodeTree):
+    """Phong"""
+
+    bl_idname = "PovrayPhongNode"
+    bl_label = "Phong"
+    bl_icon = "MESH_UVSPHERE"
+
+    def init(self, context):
+
+        albedo = self.inputs.new("NodeSocketBool", "Albedo")
+        intensity = self.inputs.new("PovraySocketFloat_0_1", "Intensity")
+        intensity.default_value = 0.8
+        phong_size = self.inputs.new("PovraySocketInt_0_256", "Size")
+        phong_size.default_value = 60
+        metallic = self.inputs.new("PovraySocketFloat_0_1", "Metallic")
+
+        self.outputs.new("NodeSocketVector", "Phong")
+
+    def draw_label(self):
+        return "Phong"
+
+
+class PovraySpecularNode(Node, nodes_properties.ObjectNodeTree):
+    """Specular"""
+
+    bl_idname = "PovraySpecularNode"
+    bl_label = "Specular"
+    bl_icon = "MESH_UVSPHERE"
+
+    def init(self, context):
+
+        albedo = self.inputs.new("NodeSocketBool", "Albedo")
+        intensity = self.inputs.new("PovraySocketFloat_0_1", "Intensity")
+        intensity.default_value = 0.8
+        roughness = self.inputs.new("PovraySocketFloat_0_1", "Roughness")
+        roughness.default_value = 0.02
+        metallic = self.inputs.new("PovraySocketFloat_0_1", "Metallic")
+
+        self.outputs.new("NodeSocketVector", "Specular")
+
+    def draw_label(self):
+        return "Specular"
+
+
+class PovrayMirrorNode(Node, nodes_properties.ObjectNodeTree):
+    """Mirror"""
+
+    bl_idname = "PovrayMirrorNode"
+    bl_label = "Mirror"
+    bl_icon = "SHADING_TEXTURE"
+
+    def init(self, context):
+
+        color = self.inputs.new("PovraySocketColor", "Color")
+        color.default_value = (1, 1, 1)
+        metallic = self.inputs.new("PovraySocketFloat_0_1", "Metallic")
+        metallic.default_value = 1.0
+        exponent = self.inputs.new("PovraySocketFloat_0_1", "Exponent")
+        exponent.default_value = 1.0
+        self.inputs.new("PovraySocketFloat_0_1", "Falloff")
+        self.inputs.new("NodeSocketBool", "Fresnel")
+        self.inputs.new("NodeSocketBool", "Conserve energy")
+        self.outputs.new("NodeSocketVector", "Mirror")
+
+    def draw_label(self):
+        return "Mirror"
+
+
+class PovrayAmbientNode(Node, nodes_properties.ObjectNodeTree):
+    """Ambient"""
+
+    bl_idname = "PovrayAmbientNode"
+    bl_label = "Ambient"
+    bl_icon = "SHADING_SOLID"
+
+    def init(self, context):
+
+        self.inputs.new("PovraySocketColor", "Ambient")
+
+        self.outputs.new("NodeSocketVector", "Ambient")
+
+    def draw_label(self):
+        return "Ambient"
+
+
+class PovrayIridescenceNode(Node, nodes_properties.ObjectNodeTree):
+    """Iridescence"""
+
+    bl_idname = "PovrayIridescenceNode"
+    bl_label = "Iridescence"
+    bl_icon = "MESH_UVSPHERE"
+
+    def init(self, context):
+
+        amount = self.inputs.new("NodeSocketFloat", "Amount")
+        amount.default_value = 0.25
+        thickness = self.inputs.new("NodeSocketFloat", "Thickness")
+        thickness.default_value = 1
+        self.inputs.new("NodeSocketFloat", "Turbulence")
+
+        self.outputs.new("NodeSocketVector", "Iridescence")
+
+    def draw_label(self):
+        return "Iridescence"
+
+
+class PovraySubsurfaceNode(Node, nodes_properties.ObjectNodeTree):
+    """Subsurface"""
+
+    bl_idname = "PovraySubsurfaceNode"
+    bl_label = "Subsurface"
+    bl_icon = "MESH_UVSPHERE"
+
+    def init(self, context):
+
+        translucency = self.inputs.new("NodeSocketColor", "Translucency")
+        translucency.default_value = (0, 0, 0, 1)
+        energy = self.inputs.new("PovraySocketInt_0_256", "Energy")
+        energy.default_value = 20
+        self.outputs.new("NodeSocketVector", "Translucency")
+
+    def draw_buttons(self, context, layout):
+        scene = context.scene
+        layout.prop(scene.pov, "sslt_enable", text="SSLT")
+
+    def draw_buttons_ext(self, context, layout):
+        scene = context.scene
+        layout.prop(scene.pov, "sslt_enable", text="SSLT")
+
+    def draw_label(self):
+        return "Subsurface"
+
+
+# ---------------------------------------------------------------- #
+
+
+class PovrayMappingNode(Node, nodes_properties.ObjectNodeTree):
+    """Mapping"""
+
+    bl_idname = "PovrayMappingNode"
+    bl_label = "Mapping"
+    bl_icon = "NODE_TEXTURE"
+
+    warp_type: EnumProperty(
+        name="Warp Types",
+        description="Select the type of warp",
+        items=(
+            ("cubic", "Cubic", ""),
+            ("cylindrical", "Cylindrical", ""),
+            ("planar", "Planar", ""),
+            ("spherical", "Spherical", ""),
+            ("toroidal", "Toroidal", ""),
+            ("uv_mapping", "UV", ""),
+            ("NONE", "None", "No indentation"),
+        ),
+        default="NONE",
+    )
+
+    warp_orientation: EnumProperty(
+        name="Warp Orientation",
+        description="Select the orientation of warp",
+        items=(("x", "X", ""), ("y", "Y", ""), ("z", "Z", "")),
+        default="y",
+    )
+
+    warp_dist_exp: FloatProperty(
+        name="Distance exponent", description="Distance exponent", min=0.0, max=100.0, default=1.0
+    )
+
+    warp_tor_major_radius: FloatProperty(
+        name="Major radius",
+        description="Torus is distance from major radius",
+        min=0.0,
+        max=5.0,
+        default=1.0,
+    )
+
+    def init(self, context):
+        self.outputs.new("NodeSocketVector", "Mapping")
+
+    def draw_buttons(self, context, layout):
+
+        column = layout.column()
+        column.prop(self, "warp_type", text="Warp type")
+        if self.warp_type in {"toroidal", "spherical", "cylindrical", "planar"}:
+            column.prop(self, "warp_orientation", text="Orientation")
+            column.prop(self, "warp_dist_exp", text="Exponent")
+        if self.warp_type == "toroidal":
+            column.prop(self, "warp_tor_major_radius", text="Major R")
+
+    def draw_buttons_ext(self, context, layout):
+
+        column = layout.column()
+        column.prop(self, "warp_type", text="Warp type")
+        if self.warp_type in {"toroidal", "spherical", "cylindrical", "planar"}:
+            column.prop(self, "warp_orientation", text="Orientation")
+            column.prop(self, "warp_dist_exp", text="Exponent")
+        if self.warp_type == "toroidal":
+            column.prop(self, "warp_tor_major_radius", text="Major R")
+
+    def draw_label(self):
+        return "Mapping"
+
+
+class PovrayMultiplyNode(Node, nodes_properties.ObjectNodeTree):
+    """Multiply"""
+
+    bl_idname = "PovrayMultiplyNode"
+    bl_label = "Multiply"
+    bl_icon = "SHADING_SOLID"
+
+    amount_x: FloatProperty(
+        name="X", description="Number of repeats", min=1.0, max=10000.0, default=1.0
+    )
+
+    amount_y: FloatProperty(
+        name="Y", description="Number of repeats", min=1.0, max=10000.0, default=1.0
+    )
+
+    amount_z: FloatProperty(
+        name="Z", description="Number of repeats", min=1.0, max=10000.0, default=1.0
+    )
+
+    def init(self, context):
+        self.outputs.new("NodeSocketVector", "Amount")
+
+    def draw_buttons(self, context, layout):
+
+        column = layout.column()
+        column.label(text="Amount")
+        row = column.row(align=True)
+        row.prop(self, "amount_x")
+        row.prop(self, "amount_y")
+        row.prop(self, "amount_z")
+
+    def draw_buttons_ext(self, context, layout):
+
+        column = layout.column()
+        column.label(text="Amount")
+        row = column.row(align=True)
+        row.prop(self, "amount_x")
+        row.prop(self, "amount_y")
+        row.prop(self, "amount_z")
+
+    def draw_label(self):
+        return "Multiply"
+
+
+class PovrayTransformNode(Node, nodes_properties.ObjectNodeTree):
+    """Transform"""
+
+    bl_idname = "PovrayTransformNode"
+    bl_label = "Transform"
+    bl_icon = "NODE_TEXTURE"
+
+    def init(self, context):
+
+        self.inputs.new("PovraySocketFloatUnlimited", "Translate x")
+        self.inputs.new("PovraySocketFloatUnlimited", "Translate y")
+        self.inputs.new("PovraySocketFloatUnlimited", "Translate z")
+        self.inputs.new("PovraySocketFloatUnlimited", "Rotate x")
+        self.inputs.new("PovraySocketFloatUnlimited", "Rotate y")
+        self.inputs.new("PovraySocketFloatUnlimited", "Rotate z")
+        sX = self.inputs.new("PovraySocketFloatUnlimited", "Scale x")
+        sX.default_value = 1.0
+        sY = self.inputs.new("PovraySocketFloatUnlimited", "Scale y")
+        sY.default_value = 1.0
+        sZ = self.inputs.new("PovraySocketFloatUnlimited", "Scale z")
+        sZ.default_value = 1.0
+
+        self.outputs.new("NodeSocketVector", "Transform")
+
+    def draw_label(self):
+        return "Transform"
+
+
+class PovrayValueNode(Node, nodes_properties.ObjectNodeTree):
+    """Value"""
+
+    bl_idname = "PovrayValueNode"
+    bl_label = "Value"
+    bl_icon = "SHADING_SOLID"
+
+    def init(self, context):
+
+        self.outputs.new("PovraySocketUniversal", "Value")
+
+    def draw_label(self):
+        return "Value"
+
+
+class PovrayModifierNode(Node, nodes_properties.ObjectNodeTree):
+    """Modifier"""
+
+    bl_idname = "PovrayModifierNode"
+    bl_label = "Modifier"
+    bl_icon = "NODE_TEXTURE"
+
+    def init(self, context):
+
+        turb_x = self.inputs.new("PovraySocketFloat_0_10", "Turb X")
+        turb_x.default_value = 0.1
+        turb_y = self.inputs.new("PovraySocketFloat_0_10", "Turb Y")
+        turb_y.default_value = 0.1
+        turb_z = self.inputs.new("PovraySocketFloat_0_10", "Turb Z")
+        turb_z.default_value = 0.1
+        octaves = self.inputs.new("PovraySocketInt_1_9", "Octaves")
+        octaves.default_value = 1
+        lambat = self.inputs.new("PovraySocketFloat_0_10", "Lambda")
+        lambat.default_value = 2.0
+        omega = self.inputs.new("PovraySocketFloat_0_10", "Omega")
+        omega.default_value = 0.5
+        freq = self.inputs.new("PovraySocketFloat_0_10", "Frequency")
+        freq.default_value = 2.0
+        self.inputs.new("PovraySocketFloat_0_10", "Phase")
+
+        self.outputs.new("NodeSocketVector", "Modifier")
+
+    def draw_label(self):
+        return "Modifier"
+
+
+class PovrayPigmentNode(Node, nodes_properties.ObjectNodeTree):
+    """Pigment"""
+
+    bl_idname = "PovrayPigmentNode"
+    bl_label = "Color"
+    bl_icon = "SHADING_SOLID"
+
+    def init(self, context):
+
+        color = self.inputs.new("PovraySocketColor", "Color")
+        color.default_value = (1, 1, 1)
+        pov_filter = self.inputs.new("PovraySocketFloat_0_1", "Filter")
+        transmit = self.inputs.new("PovraySocketFloat_0_1", "Transmit")
+        self.outputs.new("NodeSocketColor", "Pigment")
+
+    def draw_label(self):
+        return "Color"
+
+
+class PovrayColorImageNode(Node, nodes_properties.ObjectNodeTree):
+    """ColorImage"""
+
+    bl_idname = "PovrayColorImageNode"
+    bl_label = "Image map"
+
+    map_type: bpy.props.EnumProperty(
+        name="Map type",
+        description="",
+        items=(
+            ("uv_mapping", "UV", ""),
+            ("0", "Planar", "Default planar mapping"),
+            ("1", "Spherical", "Spherical mapping"),
+            ("2", "Cylindrical", "Cylindrical mapping"),
+            ("5", "Torroidal", "Torus or donut shaped mapping"),
+        ),
+        default="0",
+    )
+    image: StringProperty(maxlen=1024)  # , subtype="FILE_PATH"
+    interpolate: EnumProperty(
+        name="Interpolate",
+        description="Adding the interpolate keyword can smooth the jagged look of a bitmap",
+        items=(
+            ("2", "Bilinear", "Gives bilinear interpolation"),
+            ("4", "Normalized", "Gives normalized distance"),
+        ),
+        default="2",
+    )
+    premultiplied: BoolProperty(default=False)
+    once: BoolProperty(description="Not to repeat", default=False)
+
+    def init(self, context):
+
+        gamma = self.inputs.new("PovraySocketFloat_000001_10", "Gamma")
+        gamma.default_value = 2.0
+        transmit = self.inputs.new("PovraySocketFloat_0_1", "Transmit")
+        pov_filter = self.inputs.new("PovraySocketFloat_0_1", "Filter")
+        mapping = self.inputs.new("NodeSocketVector", "Mapping")
+        mapping.hide_value = True
+        transform = self.inputs.new("NodeSocketVector", "Transform")
+        transform.hide_value = True
+        modifier = self.inputs.new("NodeSocketVector", "Modifier")
+        modifier.hide_value = True
+
+        self.outputs.new("NodeSocketColor", "Pigment")
+
+    def draw_buttons(self, context, layout):
+
+        column = layout.column()
+        im = None
+        for image in bpy.data.images:
+            if image.name == self.image:
+                im = image
+        split = column.split(factor=0.8, align=True)
+        split.prop_search(self, "image", context.blend_data, "images", text="")
+        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
+        if im is not None:
+            column.prop(im, "source", text="")
+        column.prop(self, "map_type", text="")
+        column.prop(self, "interpolate", text="")
+        row = column.row()
+        row.prop(self, "premultiplied", text="Premul")
+        row.prop(self, "once", text="Once")
+
+    def draw_buttons_ext(self, context, layout):
+
+        column = layout.column()
+        im = None
+        for image in bpy.data.images:
+            if image.name == self.image:
+                im = image
+        split = column.split(factor=0.8, align=True)
+        split.prop_search(self, "image", context.blend_data, "images", text="")
+        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
+        if im is not None:
+            column.prop(im, "source", text="")
+        column.prop(self, "map_type", text="")
+        column.prop(self, "interpolate", text="")
+        row = column.row()
+        row.prop(self, "premultiplied", text="Premul")
+        row.prop(self, "once", text="Once")
+
+    def draw_label(self):
+        return "Image map"
+
+
+class PovrayBumpMapNode(Node, nodes_properties.ObjectNodeTree):
+    """BumpMap"""
+
+    bl_idname = "PovrayBumpMapNode"
+    bl_label = "Bump map"
+    bl_icon = "TEXTURE"
+
+    map_type: bpy.props.EnumProperty(
+        name="Map type",
+        description="",
+        items=(
+            ("uv_mapping", "UV", ""),
+            ("0", "Planar", "Default planar mapping"),
+            ("1", "Spherical", "Spherical mapping"),
+            ("2", "Cylindrical", "Cylindrical mapping"),
+            ("5", "Torroidal", "Torus or donut shaped mapping"),
+        ),
+        default="0",
+    )
+    image: StringProperty(maxlen=1024)  # , subtype="FILE_PATH"
+    interpolate: EnumProperty(
+        name="Interpolate",
+        description="Adding the interpolate keyword can smooth the jagged look of a bitmap",
+        items=(
+            ("2", "Bilinear", "Gives bilinear interpolation"),
+            ("4", "Normalized", "Gives normalized distance"),
+        ),
+        default="2",
+    )
+    once: BoolProperty(description="Not to repeat", default=False)
+
+    def init(self, context):
+
+        self.inputs.new("PovraySocketFloat_0_10", "Normal")
+        mapping = self.inputs.new("NodeSocketVector", "Mapping")
+        mapping.hide_value = True
+        transform = self.inputs.new("NodeSocketVector", "Transform")
+        transform.hide_value = True
+        modifier = self.inputs.new("NodeSocketVector", "Modifier")
+        modifier.hide_value = True
+
+        normal = self.outputs.new("NodeSocketFloat", "Normal")
+        normal.hide_value = True
+
+    def draw_buttons(self, context, layout):
+
+        column = layout.column()
+        im = None
+        for image in bpy.data.images:
+            if image.name == self.image:
+                im = image
+        split = column.split(factor=0.8, align=True)
+        split.prop_search(self, "image", context.blend_data, "images", text="")
+        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
+        if im is not None:
+            column.prop(im, "source", text="")
+        column.prop(self, "map_type", text="")
+        column.prop(self, "interpolate", text="")
+        column.prop(self, "once", text="Once")
+
+    def draw_buttons_ext(self, context, layout):
+
+        column = layout.column()
+        im = None
+        for image in bpy.data.images:
+            if image.name == self.image:
+                im = image
+        split = column.split(factor=0.8, align=True)
+        split.prop_search(self, "image", context.blend_data, "images", text="")
+        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
+        if im is not None:
+            column.prop(im, "source", text="")
+        column.prop(self, "map_type", text="")
+        column.prop(self, "interpolate", text="")
+        column.prop(self, "once", text="Once")
+
+    def draw_label(self):
+        return "Bump Map"
+
+
+class PovrayImagePatternNode(Node, nodes_properties.ObjectNodeTree):
+    """ImagePattern"""
+
+    bl_idname = "PovrayImagePatternNode"
+    bl_label = "Image pattern"
+    bl_icon = "NODE_TEXTURE"
+
+    map_type: bpy.props.EnumProperty(
+        name="Map type",
+        description="",
+        items=(
+            ("uv_mapping", "UV", ""),
+            ("0", "Planar", "Default planar mapping"),
+            ("1", "Spherical", "Spherical mapping"),
+            ("2", "Cylindrical", "Cylindrical mapping"),
+            ("5", "Torroidal", "Torus or donut shaped mapping"),
+        ),
+        default="0",
+    )
+    image: StringProperty(maxlen=1024)  # , subtype="FILE_PATH"
+    interpolate: EnumProperty(
+        name="Interpolate",
+        description="Adding the interpolate keyword can smooth the jagged look of a bitmap",
+        items=(
+            ("2", "Bilinear", "Gives bilinear interpolation"),
+            ("4", "Normalized", "Gives normalized distance"),
+        ),
+        default="2",
+    )
+    premultiplied: BoolProperty(default=False)
+    once: BoolProperty(description="Not to repeat", default=False)
+    use_alpha: BoolProperty(default=True)
+
+    def init(self, context):
+
+        gamma = self.inputs.new("PovraySocketFloat_000001_10", "Gamma")
+        gamma.default_value = 2.0
+
+        self.outputs.new("PovraySocketPattern", "Pattern")
+
+    def draw_buttons(self, context, layout):
+
+        column = layout.column()
+        im = None
+        for image in bpy.data.images:
+            if image.name == self.image:
+                im = image
+        split = column.split(factor=0.8, align=True)
+        split.prop_search(self, "image", context.blend_data, "images", text="")
+        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
+        if im is not None:
+            column.prop(im, "source", text="")
+        column.prop(self, "map_type", text="")
+        column.prop(self, "interpolate", text="")
+        row = column.row()
+        row.prop(self, "premultiplied", text="Premul")
+        row.prop(self, "once", text="Once")
+        column.prop(self, "use_alpha", text="Use alpha")
+
+    def draw_buttons_ext(self, context, layout):
+
+        column = layout.column()
+        im = None
+        for image in bpy.data.images:
+            if image.name == self.image:
+                im = image
+        split = column.split(factor=0.8, align=True)
+        split.prop_search(self, "image", context.blend_data, "images", text="")
+        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
+        if im is not None:
+            column.prop(im, "source", text="")
+        column.prop(self, "map_type", text="")
+        column.prop(self, "interpolate", text="")
+        row = column.row()
+        row.prop(self, "premultiplied", text="Premul")
+        row.prop(self, "once", text="Once")
+
+    def draw_label(self):
+        return "Image pattern"
+
+
+class ShaderPatternNode(Node, nodes_properties.ObjectNodeTree):
+    """Pattern"""
+
+    bl_idname = "ShaderPatternNode"
+    bl_label = "Other patterns"
+
+    pattern: EnumProperty(
+        name="Pattern",
+        description="Agate, Crackle, Gradient, Pavement, Spiral, Tiling",
+        items=(
+            ("agate", "Agate", ""),
+            ("crackle", "Crackle", ""),
+            ("gradient", "Gradient", ""),
+            ("pavement", "Pavement", ""),
+            ("spiral1", "Spiral 1", ""),
+            ("spiral2", "Spiral 2", ""),
+            ("tiling", "Tiling", ""),
+        ),
+        default="agate",
+    )
+
+    agate_turb: FloatProperty(
+        name="Agate turb", description="Agate turbulence", min=0.0, max=100.0, default=0.5
+    )
+
+    crackle_form_x: FloatProperty(
+        name="X", description="Form vector X", min=-150.0, max=150.0, default=-1
+    )
+
+    crackle_form_y: FloatProperty(
+        name="Y", description="Form vector Y", min=-150.0, max=150.0, default=1
+    )
+
+    crackle_form_z: FloatProperty(
+        name="Z", description="Form vector Z", min=-150.0, max=150.0, default=0
+    )
+
+    crackle_metric: FloatProperty(
+        name="Metric", description="Crackle metric", min=0.0, max=150.0, default=1
+    )
+
+    crackle_solid: BoolProperty(name="Solid", description="Crackle solid", default=False)
+
+    spiral_arms: FloatProperty(name="Number", description="", min=0.0, max=256.0, default=2.0)
+
+    tiling_number: IntProperty(name="Number", description="", min=1, max=27, default=1)
+
+    gradient_orient: EnumProperty(
+        name="Orient",
+        description="",
+        items=(("x", "X", ""), ("y", "Y", ""), ("z", "Z", "")),
+        default="x",
+    )
+
+    def init(self, context):
+
+        pat = self.outputs.new("PovraySocketPattern", "Pattern")
+
+    def draw_buttons(self, context, layout):
+
+        layout.prop(self, "pattern", text="")
+        if self.pattern == "agate":
+            layout.prop(self, "agate_turb")
+        if self.pattern == "crackle":
+            layout.prop(self, "crackle_metric")
+            layout.prop(self, "crackle_solid")
+            layout.label(text="Form:")
+            layout.prop(self, "crackle_form_x")
+            layout.prop(self, "crackle_form_y")
+            layout.prop(self, "crackle_form_z")
+        if self.pattern in {"spiral1", "spiral2"}:
+            layout.prop(self, "spiral_arms")
+        if self.pattern in {"tiling"}:
+            layout.prop(self, "tiling_number")
+        if self.pattern in {"gradient"}:
+            layout.prop(self, "gradient_orient")
+
+    def draw_buttons_ext(self, context, layout):
+        pass
+
+    def draw_label(self):
+        return "Other patterns"
+
+
+class ShaderTextureMapNode(Node, nodes_properties.ObjectNodeTree):
+    """Texture Map"""
+
+    bl_idname = "ShaderTextureMapNode"
+    bl_label = "Texture map"
+
+    brick_size_x: FloatProperty(name="X", description="", min=0.0000, max=1.0000, default=0.2500)
+
+    brick_size_y: FloatProperty(name="Y", description="", min=0.0000, max=1.0000, default=0.0525)
+
+    brick_size_z: FloatProperty(name="Z", description="", min=0.0000, max=1.0000, default=0.1250)
+
+    brick_mortar: FloatProperty(
+        name="Mortar", description="Mortar", min=0.000, max=1.500, default=0.01
+    )
+
+    def init(self, context):
+        mat = bpy.context.object.active_material
+        self.inputs.new("PovraySocketPattern", "")
+        color = self.inputs.new("NodeSocketColor", "Color ramp")
+        color.hide_value = True
+        for i in range(4):
+            transform = self.inputs.new("PovraySocketTransform", "Transform")
+            transform.hide_value = True
+        number = mat.pov.inputs_number
+        for i in range(number):
+            self.inputs.new("PovraySocketTexture", "%s" % i)
+
+        self.outputs.new("PovraySocketTexture", "Texture")
+
+    def draw_buttons(self, context, layout):
+
+        if self.inputs[0].default_value == "brick":
+            layout.prop(self, "brick_mortar")
+            layout.label(text="Brick size:")
+            layout.prop(self, "brick_size_x")
+            layout.prop(self, "brick_size_y")
+            layout.prop(self, "brick_size_z")
+
+    def draw_buttons_ext(self, context, layout):
+
+        if self.inputs[0].default_value == "brick":
+            layout.prop(self, "brick_mortar")
+            layout.label(text="Brick size:")
+            layout.prop(self, "brick_size_x")
+            layout.prop(self, "brick_size_y")
+            layout.prop(self, "brick_size_z")
+
+    def draw_label(self):
+        return "Texture map"
+
+
+class ShaderNormalMapNode(Node, nodes_properties.ObjectNodeTree):
+    """Normal Map"""
+
+    bl_idname = "ShaderNormalMapNode"
+    bl_label = "Normal map"
+
+    brick_size_x: FloatProperty(name="X", description="", min=0.0000, max=1.0000, default=0.2500)
+
+    brick_size_y: FloatProperty(name="Y", description="", min=0.0000, max=1.0000, default=0.0525)
+
+    brick_size_z: FloatProperty(name="Z", description="", min=0.0000, max=1.0000, default=0.1250)
+
+    brick_mortar: FloatProperty(
+        name="Mortar", description="Mortar", min=0.000, max=1.500, default=0.01
+    )
+
+    def init(self, context):
+        self.inputs.new("PovraySocketPattern", "")
+        normal = self.inputs.new("PovraySocketFloat_10", "Normal")
+        slope = self.inputs.new("PovraySocketMap", "Slope map")
+        for i in range(4):
+            transform = self.inputs.new("PovraySocketTransform", "Transform")
+            transform.hide_value = True
+        self.outputs.new("PovraySocketNormal", "Normal")
+
+    def draw_buttons(self, context, layout):
+        # for i, inp in enumerate(self.inputs):
+
+        if self.inputs[0].default_value == "brick":
+            layout.prop(self, "brick_mortar")
+            layout.label(text="Brick size:")
+            layout.prop(self, "brick_size_x")
+            layout.prop(self, "brick_size_y")
+            layout.prop(self, "brick_size_z")
+
+    def draw_buttons_ext(self, context, layout):
+
+        if self.inputs[0].default_value == "brick":
+            layout.prop(self, "brick_mortar")
+            layout.label(text="Brick size:")
+            layout.prop(self, "brick_size_x")
+            layout.prop(self, "brick_size_y")
+            layout.prop(self, "brick_size_z")
+
+    def draw_label(self):
+        return "Normal map"
+
+
+class ShaderNormalMapEntryNode(Node, nodes_properties.ObjectNodeTree):
+    """Normal Map Entry"""
+
+    bl_idname = "ShaderNormalMapEntryNode"
+    bl_label = "Normal map entry"
+
+    def init(self, context):
+        self.inputs.new("PovraySocketFloat_0_1", "Stop")
+        self.inputs.new("PovraySocketFloat_0_1", "Gray")
+
+    def draw_label(self):
+        return "Normal map entry"
+
+
+class IsoPropsNode(Node, CompositorNodeTree):
+    """ISO Props"""
+
+    bl_idname = "IsoPropsNode"
+    bl_label = "Iso"
+    node_label: StringProperty(maxlen=1024)
+
+    def init(self, context):
+        ob = bpy.context.object
+        self.node_label = ob.name
+        text_name = ob.pov.function_text
+        if text_name:
+            text = bpy.data.texts[text_name]
+            for line in text.lines:
+                split = line.body.split()
+                if split[0] == "#declare":
+                    socket = self.inputs.new("NodeSocketFloat", "%s" % split[1])
+                    value = split[3].split(";")
+                    value = value[0]
+                    socket.default_value = float(value)
+
+    def draw_label(self):
+        return self.node_label
+
+
+class PovrayFogNode(Node, CompositorNodeTree):
+    """Fog settings"""
+
+    bl_idname = "PovrayFogNode"
+    bl_label = "Fog"
+
+    def init(self, context):
+        color = self.inputs.new("NodeSocketColor", "Color")
+        color.default_value = (0.7, 0.7, 0.7, 0.25)
+        self.inputs.new("PovraySocketFloat_0_1", "Filter")
+        distance = self.inputs.new("NodeSocketInt", "Distance")
+        distance.default_value = 150
+        self.inputs.new("NodeSocketBool", "Ground")
+        fog_offset = self.inputs.new("NodeSocketFloat", "Offset")
+        fog_alt = self.inputs.new("NodeSocketFloat", "Altitude")
+        turb = self.inputs.new("NodeSocketVector", "Turbulence")
+        turb_depth = self.inputs.new("PovraySocketFloat_0_10", "Depth")
+        turb_depth.default_value = 0.5
+        octaves = self.inputs.new("PovraySocketInt_1_9", "Octaves")
+        octaves.default_value = 5
+        lambdat = self.inputs.new("PovraySocketFloat_0_10", "Lambda")
+        lambdat.default_value = 1.25
+        omega = self.inputs.new("PovraySocketFloat_0_10", "Omega")
+        omega.default_value = 0.35
+        translate = self.inputs.new("NodeSocketVector", "Translate")
+        rotate = self.inputs.new("NodeSocketVector", "Rotate")
+        scale = self.inputs.new("NodeSocketVector", "Scale")
+        scale.default_value = (1, 1, 1)
+
+    def draw_label(self):
+        return "Fog"
+
+
+class PovraySlopeNode(Node, TextureNodeTree):
+    """Output"""
+
+    bl_idname = "PovraySlopeNode"
+    bl_label = "Slope Map"
+
+    def init(self, context):
+        self.use_custom_color = True
+        self.color = (0, 0.2, 0)
+        slope = self.inputs.new("PovraySocketSlope", "0")
+        slope = self.inputs.new("PovraySocketSlope", "1")
+        slopemap = self.outputs.new("PovraySocketMap", "Slope map")
+        slopemap.hide_value = True
+
+    def draw_buttons(self, context, layout):
+
+        layout.operator("pov.nodeinputadd")
+        row = layout.row()
+        row.label(text="Value")
+        row.label(text="Height")
+        row.label(text="Slope")
+
+    def draw_buttons_ext(self, context, layout):
+
+        layout.operator("pov.nodeinputadd")
+        row = layout.row()
+        row.label(text="Value")
+        row.label(text="Height")
+        row.label(text="Slope")
+
+    def draw_label(self):
+        return "Slope Map"
+
+
+# -------- Texture nodes
+class TextureOutputNode(Node, TextureNodeTree):
+    """Output"""
+
+    bl_idname = "TextureOutputNode"
+    bl_label = "Color Map"
+
+    def init(self, context):
+        tex = bpy.context.object.active_material.active_texture
+        num_sockets = int(tex.pov.density_lines / 32)
+        for i in range(num_sockets):
+            color = self.inputs.new("NodeSocketColor", "%s" % i)
+            color.hide_value = True
+
+    def draw_buttons(self, context, layout):
+
+        layout.label(text="Color Ramps:")
+
+    def draw_label(self):
+        return "Color Map"
+
+
+classes = (
+    PovrayOutputNode,
+    PovrayTextureNode,
+    PovrayFinishNode,
+    PovrayDiffuseNode,
+    PovrayPhongNode,
+    PovraySpecularNode,
+    PovrayMirrorNode,
+    PovrayAmbientNode,
+    PovrayIridescenceNode,
+    PovraySubsurfaceNode,
+    PovrayMappingNode,
+    PovrayMultiplyNode,
+    PovrayTransformNode,
+    PovrayValueNode,
+    PovrayModifierNode,
+    PovrayPigmentNode,
+    PovrayColorImageNode,
+    PovrayBumpMapNode,
+    PovrayImagePatternNode,
+    ShaderPatternNode,
+    ShaderTextureMapNode,
+    ShaderNormalMapNode,
+    ShaderNormalMapEntryNode,
+    IsoPropsNode,
+    PovrayFogNode,
+    PovraySlopeNode,
+    TextureOutputNode,
+)
+
+
+def register():
+    for cls in classes:
+        register_class(cls)
+
+
+def unregister():
+    for cls in reversed(classes):
+        unregister_class(cls)
diff --git a/render_povray/nodes_fn.py b/render_povray/nodes_fn.py
new file mode 100644
index 000000000..ef12032f5
--- /dev/null
+++ b/render_povray/nodes_fn.py
@@ -0,0 +1,704 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""Translate complex shaders to exported POV textures."""
+
+import bpy
+
+# WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+def write_nodes(pov_mat_name, ntree, file):
+    """Translate Blender node trees to pov and write them to file."""
+    # such function local inlined import are official guidelines
+    # of Blender Foundation to lighten addons footprint at startup
+    from os import path
+    from .render import string_strip_hyphen
+
+    declare_nodes = []
+    scene = bpy.context.scene
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayFinishNode" and node.outputs["Finish"].is_linked:
+            file.write("#declare %s = finish {\n" % pov_node_name)
+            emission = node.inputs["Emission"].default_value
+            if node.inputs["Emission"].is_linked:
+                pass
+            file.write("    emission %.4g\n" % emission)
+            for link in ntree.links:
+                if link.to_node == node:
+
+                    if link.from_node.bl_idname == "PovrayDiffuseNode":
+                        intensity = 0
+                        albedo = ""
+                        brilliance = 0
+                        crand = 0
+                        if link.from_node.inputs["Intensity"].is_linked:
+                            pass
+                        else:
+                            intensity = link.from_node.inputs["Intensity"].default_value
+                        if link.from_node.inputs["Albedo"].is_linked:
+                            pass
+                        else:
+                            if link.from_node.inputs["Albedo"].default_value:
+                                albedo = "albedo"
+                        file.write("    diffuse %s %.4g\n" % (albedo, intensity))
+                        if link.from_node.inputs["Brilliance"].is_linked:
+                            pass
+                        else:
+                            brilliance = link.from_node.inputs["Brilliance"].default_value
+                        file.write("    brilliance %.4g\n" % brilliance)
+                        if link.from_node.inputs["Crand"].is_linked:
+                            pass
+                        else:
+                            crand = link.from_node.inputs["Crand"].default_value
+                        if crand > 0:
+                            file.write("    crand %.4g\n" % crand)
+
+                    if link.from_node.bl_idname == "PovraySubsurfaceNode":
+                        if scene.povray.sslt_enable:
+                            energy = 0
+                            r = g = b = 0
+                            if link.from_node.inputs["Translucency"].is_linked:
+                                pass
+                            else:
+                                r, g, b, a = link.from_node.inputs["Translucency"].default_value[:]
+                            if link.from_node.inputs["Energy"].is_linked:
+                                pass
+                            else:
+                                energy = link.from_node.inputs["Energy"].default_value
+                            file.write(
+                                "    subsurface { translucency <%.4g,%.4g,%.4g>*%s }\n"
+                                % (r, g, b, energy)
+                            )
+
+                    if link.from_node.bl_idname in {"PovraySpecularNode", "PovrayPhongNode"}:
+                        intensity = 0
+                        albedo = ""
+                        roughness = 0
+                        metallic = 0
+                        phong_size = 0
+                        highlight = "specular"
+                        if link.from_node.inputs["Intensity"].is_linked:
+                            pass
+                        else:
+                            intensity = link.from_node.inputs["Intensity"].default_value
+
+                        if link.from_node.inputs["Albedo"].is_linked:
+                            pass
+                        else:
+                            if link.from_node.inputs["Albedo"].default_value:
+                                albedo = "albedo"
+                        if link.from_node.bl_idname in {"PovrayPhongNode"}:
+                            highlight = "phong"
+                        file.write("    %s %s %.4g\n" % (highlight, albedo, intensity))
+
+                        if link.from_node.bl_idname in {"PovraySpecularNode"}:
+                            if link.from_node.inputs["Roughness"].is_linked:
+                                pass
+                            else:
+                                roughness = link.from_node.inputs["Roughness"].default_value
+                            file.write("    roughness %.6g\n" % roughness)
+
+                        if link.from_node.bl_idname in {"PovrayPhongNode"}:
+                            if link.from_node.inputs["Size"].is_linked:
+                                pass
+                            else:
+                                phong_size = link.from_node.inputs["Size"].default_value
+                            file.write("    phong_size %s\n" % phong_size)
+
+                        if link.from_node.inputs["Metallic"].is_linked:
+                            pass
+                        else:
+                            metallic = link.from_node.inputs["Metallic"].default_value
+                        file.write("    metallic %.4g\n" % metallic)
+
+                    if link.from_node.bl_idname in {"PovrayMirrorNode"}:
+                        file.write("    reflection {\n")
+                        color = None
+                        exponent = 0
+                        metallic = 0
+                        falloff = 0
+                        fresnel = ""
+                        conserve = ""
+                        if link.from_node.inputs["Color"].is_linked:
+                            pass
+                        else:
+                            color = link.from_node.inputs["Color"].default_value[:]
+                        file.write("    <%.4g,%.4g,%.4g>\n" % (color[0], color[1], color[2]))
+
+                        if link.from_node.inputs["Exponent"].is_linked:
+                            pass
+                        else:
+                            exponent = link.from_node.inputs["Exponent"].default_value
+                        file.write("    exponent %.4g\n" % exponent)
+
+                        if link.from_node.inputs["Falloff"].is_linked:
+                            pass
+                        else:
+                            falloff = link.from_node.inputs["Falloff"].default_value
+                        file.write("    falloff %.4g\n" % falloff)
+
+                        if link.from_node.inputs["Metallic"].is_linked:
+                            pass
+                        else:
+                            metallic = link.from_node.inputs["Metallic"].default_value
+                        file.write("    metallic %.4g" % metallic)
+
+                        if link.from_node.inputs["Fresnel"].is_linked:
+                            pass
+                        else:
+                            if link.from_node.inputs["Fresnel"].default_value:
+                                fresnel = "fresnel"
+
+                        if link.from_node.inputs["Conserve energy"].is_linked:
+                            pass
+                        else:
+                            if link.from_node.inputs["Conserve energy"].default_value:
+                                conserve = "conserve_energy"
+
+                        file.write("    %s}\n    %s\n" % (fresnel, conserve))
+
+                    if link.from_node.bl_idname == "PovrayAmbientNode":
+                        ambient = (0, 0, 0)
+                        if link.from_node.inputs["Ambient"].is_linked:
+                            pass
+                        else:
+                            ambient = link.from_node.inputs["Ambient"].default_value[:]
+                        file.write("    ambient <%.4g,%.4g,%.4g>\n" % ambient)
+
+                    if link.from_node.bl_idname in {"PovrayIridescenceNode"}:
+                        file.write("    irid {\n")
+                        amount = 0
+                        thickness = 0
+                        turbulence = 0
+                        if link.from_node.inputs["Amount"].is_linked:
+                            pass
+                        else:
+                            amount = link.from_node.inputs["Amount"].default_value
+                        file.write("    %.4g\n" % amount)
+
+                        if link.from_node.inputs["Thickness"].is_linked:
+                            pass
+                        else:
+                            exponent = link.from_node.inputs["Thickness"].default_value
+                        file.write("    thickness %.4g\n" % thickness)
+
+                        if link.from_node.inputs["Turbulence"].is_linked:
+                            pass
+                        else:
+                            falloff = link.from_node.inputs["Turbulence"].default_value
+                        file.write("    turbulence %.4g}\n" % turbulence)
+
+            file.write("}\n")
+
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayTransformNode" and node.outputs["Transform"].is_linked:
+            tx = node.inputs["Translate x"].default_value
+            ty = node.inputs["Translate y"].default_value
+            tz = node.inputs["Translate z"].default_value
+            rx = node.inputs["Rotate x"].default_value
+            ry = node.inputs["Rotate y"].default_value
+            rz = node.inputs["Rotate z"].default_value
+            sx = node.inputs["Scale x"].default_value
+            sy = node.inputs["Scale y"].default_value
+            sz = node.inputs["Scale z"].default_value
+            file.write(
+                "#declare %s = transform {\n"
+                "    translate<%.4g,%.4g,%.4g>\n"
+                "    rotate<%.4g,%.4g,%.4g>\n"
+                "    scale<%.4g,%.4g,%.4g>}\n" % (pov_node_name, tx, ty, tz, rx, ry, rz, sx, sy, sz)
+            )
+
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayColorImageNode" and node.outputs["Pigment"].is_linked:
+            declare_nodes.append(node.name)
+            if node.image == "":
+                file.write("#declare %s = pigment { color rgb 0.8}\n" % pov_node_name)
+            else:
+                im = bpy.data.images[node.image]
+                if im.filepath and path.exists(bpy.path.abspath(im.filepath)):  # (os.path)
+                    transform = ""
+                    for link in ntree.links:
+                        if (
+                            link.from_node.bl_idname == "PovrayTransformNode"
+                            and link.to_node == node
+                        ):
+                            pov_trans_name = (
+                                string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
+                                + "_%s" % pov_mat_name
+                            )
+                            transform = "transform {%s}" % pov_trans_name
+                    uv = ""
+                    if node.map_type == "uv_mapping":
+                        uv = "uv_mapping"
+                    filepath = bpy.path.abspath(im.filepath)
+                    file.write("#declare %s = pigment {%s image_map {\n" % (pov_node_name, uv))
+                    premul = "off"
+                    if node.premultiplied:
+                        premul = "on"
+                    once = ""
+                    if node.once:
+                        once = "once"
+                    file.write(
+                        '    "%s"\n    gamma %.6g\n    premultiplied %s\n'
+                        % (filepath, node.inputs["Gamma"].default_value, premul)
+                    )
+                    file.write("    %s\n" % once)
+                    if node.map_type != "uv_mapping":
+                        file.write("    map_type %s\n" % node.map_type)
+                    file.write(
+                        "    interpolate %s\n    filter all %.4g\n    transmit all %.4g\n"
+                        % (
+                            node.interpolate,
+                            node.inputs["Filter"].default_value,
+                            node.inputs["Transmit"].default_value,
+                        )
+                    )
+                    file.write("    }\n")
+                    file.write("    %s\n" % transform)
+                    file.write("    }\n")
+
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayImagePatternNode" and node.outputs["Pattern"].is_linked:
+            declare_nodes.append(node.name)
+            if node.image != "":
+                im = bpy.data.images[node.image]
+                if im.filepath and path.exists(bpy.path.abspath(im.filepath)):
+                    transform = ""
+                    for link in ntree.links:
+                        if (
+                            link.from_node.bl_idname == "PovrayTransformNode"
+                            and link.to_node == node
+                        ):
+                            pov_trans_name = (
+                                string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
+                                + "_%s" % pov_mat_name
+                            )
+                            transform = "transform {%s}" % pov_trans_name
+                    uv = ""
+                    if node.map_type == "uv_mapping":
+                        uv = "uv_mapping"
+                    filepath = bpy.path.abspath(im.filepath)
+                    file.write("#macro %s() %s image_pattern {\n" % (pov_node_name, uv))
+                    premul = "off"
+                    if node.premultiplied:
+                        premul = "on"
+                    once = ""
+                    if node.once:
+                        once = "once"
+                    file.write(
+                        '    "%s"\n    gamma %.6g\n    premultiplied %s\n'
+                        % (filepath, node.inputs["Gamma"].default_value, premul)
+                    )
+                    file.write("    %s\n" % once)
+                    if node.map_type != "uv_mapping":
+                        file.write("    map_type %s\n" % node.map_type)
+                    file.write("    interpolate %s\n" % node.interpolate)
+                    file.write("    }\n")
+                    file.write("    %s\n" % transform)
+                    file.write("#end\n")
+
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayBumpMapNode" and node.outputs["Normal"].is_linked:
+            if node.image != "":
+                im = bpy.data.images[node.image]
+                if im.filepath and path.exists(bpy.path.abspath(im.filepath)):
+                    transform = ""
+                    for link in ntree.links:
+                        if (
+                            link.from_node.bl_idname == "PovrayTransformNode"
+                            and link.to_node == node
+                        ):
+                            pov_trans_name = (
+                                string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
+                                + "_%s" % pov_mat_name
+                            )
+                            transform = "transform {%s}" % pov_trans_name
+                    uv = ""
+                    if node.map_type == "uv_mapping":
+                        uv = "uv_mapping"
+                    filepath = bpy.path.abspath(im.filepath)
+                    file.write("#declare %s = normal {%s bump_map {\n" % (pov_node_name, uv))
+                    once = ""
+                    if node.once:
+                        once = "once"
+                    file.write('    "%s"\n' % filepath)
+                    file.write("    %s\n" % once)
+                    if node.map_type != "uv_mapping":
+                        file.write("    map_type %s\n" % node.map_type)
+                    bump_size = node.inputs["Normal"].default_value
+                    if node.inputs["Normal"].is_linked:
+                        pass
+                    file.write(
+                        "    interpolate %s\n    bump_size %.4g\n" % (node.interpolate, bump_size)
+                    )
+                    file.write("    }\n")
+                    file.write("    %s\n" % transform)
+                    file.write("    }\n")
+                    declare_nodes.append(node.name)
+
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayPigmentNode" and node.outputs["Pigment"].is_linked:
+            declare_nodes.append(node.name)
+            r, g, b = node.inputs["Color"].default_value[:]
+            f = node.inputs["Filter"].default_value
+            t = node.inputs["Transmit"].default_value
+            if node.inputs["Color"].is_linked:
+                pass
+            file.write(
+                "#declare %s = pigment{color srgbft <%.4g,%.4g,%.4g,%.4g,%.4g>}\n"
+                % (pov_node_name, r, g, b, f, t)
+            )
+
+    for node in ntree.nodes:
+        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
+        if node.bl_idname == "PovrayTextureNode" and node.outputs["Texture"].is_linked:
+            declare_nodes.append(node.name)
+            r, g, b = node.inputs["Pigment"].default_value[:]
+            pov_col_name = "color rgb <%.4g,%.4g,%.4g>" % (r, g, b)
+            if node.inputs["Pigment"].is_linked:
+                for link in ntree.links:
+                    if link.to_node == node and link.to_socket.name == "Pigment":
+                        pov_col_name = (
+                            string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
+                            + "_%s" % pov_mat_name
+                        )
+            file.write("#declare %s = texture{\n    pigment{%s}\n" % (pov_node_name, pov_col_name))
+            if node.inputs["Normal"].is_linked:
+                for link in ntree.links:
+                    if (
+                        link.to_node == node
+                        and link.to_socket.name == "Normal"
+                        and link.from_node.name in declare_nodes
+                    ):
+                        pov_nor_name = (
+                            string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
+                            + "_%s" % pov_mat_name
+                        )
+                        file.write("    normal{%s}\n" % pov_nor_name)
+            if node.inputs["Finish"].is_linked:
+                for link in ntree.links:
+                    if link.to_node == node and link.to_socket.name == "Finish":
+                        pov_fin_name = (
+                            string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
+                            + "_%s" % pov_mat_name
+                        )
+                        file.write("    finish{%s}\n" % pov_fin_name)
+            file.write("}\n")
+            declare_nodes.append(node.name)
+
+    for i in range(0, len(ntree.nodes)):
+        for node in ntree.nodes:
+            if node.bl_idname in {"ShaderNodeGroup", "ShaderTextureMapNode"}:
+                for output in node.outputs:
+                    if (
+                        output.name == "Texture"
+                        and output.is_linked
+                        and (node.name not in declare_nodes)
+                    ):
+                        declare = True
+                        for link in ntree.links:
+                            if link.to_node == node and link.to_socket.name not in {
+                                "",
+                                "Color ramp",
+                                "Mapping",
+                                "Transform",
+                                "Modifier",
+                            }:
+                                if link.from_node.name not in declare_nodes:
+                                    declare = False
+                        if declare:
+                            pov_node_name = (
+                                string_strip_hyphen(bpy.path.clean_name(node.name))
+                                + "_%s" % pov_mat_name
+                            )
+                            uv = ""
+                            warp = ""
+                            for link in ntree.links:
+                                if (
+                                    link.to_node == node
+                                    and link.from_node.bl_idname == "PovrayMappingNode"
+                                    and link.from_node.warp_type != "NONE"
+                                ):
+                                    w_type = link.from_node.warp_type
+                                    if w_type == "uv_mapping":
+                                        uv = "uv_mapping"
+                                    else:
+                                        tor = ""
+                                        if w_type == "toroidal":
+                                            tor = (
+                                                "major_radius %.4g"
+                                                % link.from_node.warp_tor_major_radius
+                                            )
+                                        orient = link.from_node.warp_orientation
+                                        exp = link.from_node.warp_dist_exp
+                                        warp = "warp{%s orientation %s dist_exp %.4g %s}" % (
+                                            w_type,
+                                            orient,
+                                            exp,
+                                            tor,
+                                        )
+                                        if link.from_node.warp_type == "planar":
+                                            warp = "warp{%s %s %.4g}" % (w_type, orient, exp)
+                                        if link.from_node.warp_type == "cubic":
+                                            warp = "warp{%s}" % w_type
+                            file.write("#declare %s = texture {%s\n" % (pov_node_name, uv))
+                            pattern = node.inputs[0].default_value
+                            advanced = ""
+                            if node.inputs[0].is_linked:
+                                for link in ntree.links:
+                                    if (
+                                        link.to_node == node
+                                        and link.from_node.bl_idname == "ShaderPatternNode"
+                                    ):
+                                        # ------------ advanced ------------------------- #
+                                        lfn = link.from_node
+                                        pattern = lfn.pattern
+                                        if pattern == "agate":
+                                            advanced = "agate_turb %.4g" % lfn.agate_turb
+                                        if pattern == "crackle":
+                                            advanced = "form <%.4g,%.4g,%.4g>" % (
+                                                lfn.crackle_form_x,
+                                                lfn.crackle_form_y,
+                                                lfn.crackle_form_z,
+                                            )
+                                            advanced += " metric %.4g" % lfn.crackle_metric
+                                            if lfn.crackle_solid:
+                                                advanced += " solid"
+                                        if pattern in {"spiral1", "spiral2"}:
+                                            advanced = "%.4g" % lfn.spiral_arms
+                                        if pattern in {"tiling"}:
+                                            advanced = "%.4g" % lfn.tiling_number
+                                        if pattern in {"gradient"}:
+                                            advanced = "%s" % lfn.gradient_orient
+                                    if (
+                                        link.to_node == node
+                                        and link.from_node.bl_idname == "PovrayImagePatternNode"
+                                    ):
+                                        pov_macro_name = (
+                                            string_strip_hyphen(
+                                                bpy.path.clean_name(link.from_node.name)
+                                            )
+                                            + "_%s" % pov_mat_name
+                                        )
+                                        pattern = "%s()" % pov_macro_name
+                            file.write("    %s %s %s\n" % (pattern, advanced, warp))
+
+                            repeat = ""
+                            for link in ntree.links:
+                                if (
+                                    link.to_node == node
+                                    and link.from_node.bl_idname == "PovrayMultiplyNode"
+                                ):
+                                    if link.from_node.amount_x > 1:
+                                        repeat += "warp{repeat %.4g * x}" % link.from_node.amount_x
+                                    if link.from_node.amount_y > 1:
+                                        repeat += " warp{repeat %.4g * y}" % link.from_node.amount_y
+                                    if link.from_node.amount_z > 1:
+                                        repeat += " warp{repeat %.4g * z}" % link.from_node.amount_z
+
+                            transform = ""
+                            for link in ntree.links:
+                                if (
+                                    link.to_node == node
+                                    and link.from_node.bl_idname == "PovrayTransformNode"
+                                ):
+                                    pov_trans_name = (
+                                        string_strip_hyphen(
+                                            bpy.path.clean_name(link.from_node.name)
+                                        )
+                                        + "_%s" % pov_mat_name
+                                    )
+                                    transform = "transform {%s}" % pov_trans_name
+                            x = 0
+                            y = 0
+                            z = 0
+                            d = 0
+                            e = 0
+                            f = 0
+                            g = 0
+                            h = 0
+                            modifier = False
+                            for link in ntree.links:
+                                if (
+                                    link.to_node == node
+                                    and link.from_node.bl_idname == "PovrayModifierNode"
+                                ):
+                                    modifier = True
+                                    if link.from_node.inputs["Turb X"].is_linked:
+                                        pass
+                                    else:
+                                        x = link.from_node.inputs["Turb X"].default_value
+
+                                    if link.from_node.inputs["Turb Y"].is_linked:
+                                        pass
+                                    else:
+                                        y = link.from_node.inputs["Turb Y"].default_value
+
+                                    if link.from_node.inputs["Turb Z"].is_linked:
+                                        pass
+                                    else:
+                                        z = link.from_node.inputs["Turb Z"].default_value
+
+                                    if link.from_node.inputs["Octaves"].is_linked:
+                                        pass
+                                    else:
+                                        d = link.from_node.inputs["Octaves"].default_value
+
+                                    if link.from_node.inputs["Lambda"].is_linked:
+                                        pass
+                                    else:
+                                        e = link.from_node.inputs["Lambda"].default_value
+
+                                    if link.from_node.inputs["Omega"].is_linked:
+                                        pass
+                                    else:
+                                        f = link.from_node.inputs["Omega"].default_value
+
+                                    if link.from_node.inputs["Frequency"].is_linked:
+                                        pass
+                                    else:
+                                        g = link.from_node.inputs["Frequency"].default_value
+
+                                    if link.from_node.inputs["Phase"].is_linked:
+                                        pass
+                                    else:
+                                        h = link.from_node.inputs["Phase"].default_value
+
+                            turb = "turbulence <%.4g,%.4g,%.4g>" % (x, y, z)
+                            octv = "octaves %s" % d
+                            lmbd = "lambda %.4g" % e
+                            omg = "omega %.4g" % f
+                            freq = "frequency %.4g" % g
+                            pha = "phase %.4g" % h
+
+                            file.write("\n")
+                            if pattern not in {
+                                "checker",
+                                "hexagon",
+                                "square",
+                                "triangular",
+                                "brick",
+                            }:
+                                file.write("    texture_map {\n")
+                            if node.inputs["Color ramp"].is_linked:
+                                for link in ntree.links:
+                                    if (
+                                        link.to_node == node
+                                        and link.from_node.bl_idname == "ShaderNodeValToRGB"
+                                    ):
+                                        els = link.from_node.color_ramp.elements
+                                        n = -1
+                                        for el in els:
+                                            n += 1
+                                            pov_in_mat_name = string_strip_hyphen(
+                                                bpy.path.clean_name(link.from_node.name)
+                                            ) + "_%s_%s" % (n, pov_mat_name)
+                                            default = True
+                                            for ilink in ntree.links:
+                                                if (
+                                                    ilink.to_node == node
+                                                    and ilink.to_socket.name == str(n)
+                                                ):
+                                                    default = False
+                                                    pov_in_mat_name = (
+                                                        string_strip_hyphen(
+                                                            bpy.path.clean_name(
+                                                                ilink.from_node.name
+                                                            )
+                                                        )
+                                                        + "_%s" % pov_mat_name
+                                                    )
+                                            if default:
+                                                r, g, b, a = el.color[:]
+                                                file.write(
+                                                    "    #declare %s = texture{"
+                                                    "pigment{"
+                                                    "color srgbt <%.4g,%.4g,%.4g,%.4g>}};\n"
+                                                    % (pov_in_mat_name, r, g, b, 1 - a)
+                                                )
+                                            file.write(
+                                                "    [%s %s]\n" % (el.position, pov_in_mat_name)
+                                            )
+                            else:
+                                els = [[0, 0, 0, 0], [1, 1, 1, 1]]
+                                for t in range(0, 2):
+                                    pov_in_mat_name = string_strip_hyphen(
+                                        bpy.path.clean_name(link.from_node.name)
+                                    ) + "_%s_%s" % (t, pov_mat_name)
+                                    default = True
+                                    for ilink in ntree.links:
+                                        if ilink.to_node == node and ilink.to_socket.name == str(t):
+                                            default = False
+                                            pov_in_mat_name = (
+                                                string_strip_hyphen(
+                                                    bpy.path.clean_name(ilink.from_node.name)
+                                                )
+                                                + "_%s" % pov_mat_name
+                                            )
+                                    if default:
+                                        r, g, b = els[t][1], els[t][2], els[t][3]
+                                        if pattern not in {
+                                            "checker",
+                                            "hexagon",
+                                            "square",
+                                            "triangular",
+                                            "brick",
+                                        }:
+                                            file.write(
+                                                "    #declare %s = texture{pigment{color rgb <%.4g,%.4g,%.4g>}};\n"
+                                                % (pov_in_mat_name, r, g, b)
+                                            )
+                                        else:
+                                            file.write(
+                                                "    texture{pigment{color rgb <%.4g,%.4g,%.4g>}}\n"
+                                                % (r, g, b)
+                                            )
+                                    if pattern not in {
+                                        "checker",
+                                        "hexagon",
+                                        "square",
+                                        "triangular",
+                                        "brick",
+                                    }:
+                                        file.write("    [%s %s]\n" % (els[t][0], pov_in_mat_name))
+                                    else:
+                                        if not default:
+                                            file.write("    texture{%s}\n" % pov_in_mat_name)
+                            if pattern not in {
+                                "checker",
+                                "hexagon",
+                                "square",
+                                "triangular",
+                                "brick",
+                            }:
+                                file.write("}\n")
+                            if pattern == "brick":
+                                file.write(
+                                    "brick_size <%.4g, %.4g, %.4g> mortar %.4g \n"
+                                    % (
+                                        node.brick_size_x,
+                                        node.brick_size_y,
+                                        node.brick_size_z,
+                                        node.brick_mortar,
+                                    )
+                                )
+                            file.write("    %s %s" % (repeat, transform))
+                            if modifier:
+                                file.write(
+                                    " %s %s %s %s %s %s" % (turb, octv, lmbd, omg, freq, pha)
+                                )
+                            file.write("}\n")
+                            declare_nodes.append(node.name)
+
+    for link in ntree.links:
+        if link.to_node.bl_idname == "PovrayOutputNode" and link.from_node.name in declare_nodes:
+            pov_mat_node_name = (
+                string_strip_hyphen(bpy.path.clean_name(link.from_node.name)) + "_%s" % pov_mat_name
+            )
+            file.write("#declare %s = %s\n" % (pov_mat_name, pov_mat_node_name))
diff --git a/render_povray/nodes_gui.py b/render_povray/nodes_gui.py
new file mode 100644
index 000000000..64fcc130d
--- /dev/null
+++ b/render_povray/nodes_gui.py
@@ -0,0 +1,278 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+""""Nodes based User interface for shaders exported to POV textures."""
+import bpy
+
+from bpy.utils import register_class, unregister_class
+from bpy.types import Menu, Operator
+from bpy.props import (
+    StringProperty,
+)
+
+# def find_node_input(node, name):
+# for input in node.inputs:
+# if input.name == name:
+# return input
+
+# def panel_node_draw(layout, id_data, output_type, input_name):
+# if not id_data.use_nodes:
+# #layout.operator("pov.material_use_nodes", icon='SOUND')#'NODETREE')
+# #layout.operator("pov.use_shading_nodes", icon='NODETREE')
+# layout.operator("WM_OT_context_toggle", icon='NODETREE').data_path = \
+# "material.pov.material_use_nodes"
+# return False
+
+# ntree = id_data.node_tree
+
+# node = find_node(id_data, output_type)
+# if not node:
+# layout.label(text="No output node")
+# else:
+# input = find_node_input(node, input_name)
+# layout.template_node_view(ntree, node, input)
+
+# return True
+
+
+class NODE_MT_POV_map_create(Menu):
+    """Create maps"""
+
+    bl_idname = "POVRAY_MT_node_map_create"
+    bl_label = "Create map"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator("node.map_create")
+
+
+def menu_func_nodes(self, context):
+    ob = context.object
+    if hasattr(ob, "active_material"):
+        mat = context.object.active_material
+        if mat and context.space_data.tree_type == "ObjectNodeTree":
+            self.layout.prop(mat.pov, "material_use_nodes")
+            self.layout.menu(NODE_MT_POV_map_create.bl_idname)
+            self.layout.operator("wm.updatepreviewkey")
+        if hasattr(mat, "active_texture") and context.scene.render.engine == "POVRAY_RENDER":
+            tex = mat.active_texture
+            if tex and context.space_data.tree_type == "TextureNodeTree":
+                self.layout.prop(tex.pov, "texture_use_nodes")
+
+
+# ------------------------------------------------------------------------------ #
+# --------------------------------- Operators ---------------------------------- #
+# ------------------------------------------------------------------------------ #
+
+
+class NODE_OT_iso_add(Operator):
+    bl_idname = "pov.nodeisoadd"
+    bl_label = "Create iso props"
+
+    def execute(self, context):
+        ob = bpy.context.object
+        if not bpy.context.scene.use_nodes:
+            bpy.context.scene.use_nodes = True
+        tree = bpy.context.scene.node_tree
+        for node in tree.nodes:
+            if node.bl_idname == "IsoPropsNode" and node.label == ob.name:
+                tree.nodes.remove(node)
+        isonode = tree.nodes.new("IsoPropsNode")
+        isonode.location = (0, 0)
+        isonode.label = ob.name
+        return {"FINISHED"}
+
+
+class NODE_OT_map_create(Operator):
+    bl_idname = "node.map_create"
+    bl_label = "Create map"
+
+    def execute(self, context):
+        x = y = 0
+        space = context.space_data
+        tree = space.edit_tree
+        for node in tree.nodes:
+            if node.select:
+                x, y = node.location
+            node.select = False
+        tmap = tree.nodes.new("ShaderTextureMapNode")
+        tmap.location = (x - 200, y)
+        return {"FINISHED"}
+
+    def invoke(self, context, event):
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self)
+
+    def draw(self, context):
+        layout = self.layout
+        mat = context.object.active_material
+        layout.prop(mat.pov, "inputs_number")
+
+
+class NODE_OT_povray_node_texture_map_add(Operator):
+    bl_idname = "pov.nodetexmapadd"
+    bl_label = "Texture map"
+
+    def execute(self, context):
+        tree = bpy.context.object.active_material.node_tree
+        tmap = tree.nodes.active
+        mtl = context.object.active_material
+        mtl.node_tree.nodes.active = tmap
+        el = tmap.color_ramp.elements.new(0.5)
+        for el in tmap.color_ramp.elements:
+            el.color = (0, 0, 0, 1)
+        for inp in tmap.inputs:
+            tmap.inputs.remove(inp)
+        for outp in tmap.outputs:
+            tmap.outputs.remove(outp)
+        pattern = tmap.inputs.new("NodeSocketVector", "Pattern")
+        pattern.hide_value = True
+        for i in range(3):
+            tmap.inputs.new("NodeSocketColor", "Shader")
+        tmap.outputs.new("NodeSocketShader", "BSDF")
+        tmap.label = "Texture Map"
+        return {"FINISHED"}
+
+
+class NODE_OT_povray_node_output_add(Operator):
+    bl_idname = "pov.nodeoutputadd"
+    bl_label = "Output"
+
+    def execute(self, context):
+        tree = bpy.context.object.active_material.node_tree
+        tmap = tree.nodes.new("ShaderNodeOutputMaterial")
+        mtl = context.object.active_material
+        mtl.node_tree.nodes.active = tmap
+        for inp in tmap.inputs:
+            tmap.inputs.remove(inp)
+        tmap.inputs.new("NodeSocketShader", "Surface")
+        tmap.label = "Output"
+        return {"FINISHED"}
+
+
+class NODE_OT_povray_node_layered_add(Operator):
+    bl_idname = "pov.nodelayeredadd"
+    bl_label = "Layered material"
+
+    def execute(self, context):
+        tree = bpy.context.object.active_material.node_tree
+        tmap = tree.nodes.new("ShaderNodeAddShader")
+        mtl = context.object.active_material
+        mtl.node_tree.nodes.active = tmap
+        tmap.label = "Layered material"
+        return {"FINISHED"}
+
+
+class NODE_OT_povray_input_add(Operator):
+    bl_idname = "pov.nodeinputadd"
+    bl_label = "Add entry"
+
+    def execute(self, context):
+        mtl = context.object.active_material
+        node = mtl.node_tree.nodes.active
+        if node.type == "VALTORGB":
+            number = 1
+            for inp in node.inputs:
+                if inp.type == "SHADER":
+                    number += 1
+            node.inputs.new("NodeSocketShader", "%s" % number)
+            els = node.color_ramp.elements
+            pos1 = els[len(els) - 1].position
+            pos2 = els[len(els) - 2].position
+            pos = (pos1 - pos2) / 2 + pos2
+            el = els.new(pos)
+
+        if node.bl_idname == "PovraySlopeNode":
+            number = len(node.inputs)
+            node.inputs.new("PovraySocketSlope", "%s" % number)
+
+        return {"FINISHED"}
+
+
+class NODE_OT_povray_input_remove(Operator):
+    bl_idname = "pov.nodeinputremove"
+    bl_label = "Remove input"
+
+    def execute(self, context):
+        mtl = context.object.active_material
+        node = mtl.node_tree.nodes.active
+        if node.type in {"VALTORGB", "ADD_SHADER"}:
+            number = len(node.inputs) - 1
+            if number > 5:
+                inp = node.inputs[number]
+                node.inputs.remove(inp)
+                if node.type == "VALTORGB":
+                    els = node.color_ramp.elements
+                    number = len(els) - 2
+                    el = els[number]
+                    els.remove(el)
+        return {"FINISHED"}
+
+
+class NODE_OT_povray_image_open(Operator):
+    bl_idname = "pov.imageopen"
+    bl_label = "Open"
+
+    filepath: StringProperty(
+        name="File Path", description="Open image", maxlen=1024, subtype="FILE_PATH"
+    )
+
+    def invoke(self, context, event):
+        context.window_manager.fileselect_add(self)
+        return {"RUNNING_MODAL"}
+
+    def execute(self, context):
+        im = bpy.data.images.load(self.filepath)
+        mtl = context.object.active_material
+        node = mtl.node_tree.nodes.active
+        node.image = im.name
+        return {"FINISHED"}
+
+
+# class TEXTURE_OT_povray_open_image(Operator):
+# bl_idname = "pov.openimage"
+# bl_label = "Open Image"
+
+# filepath = StringProperty(
+# name="File Path",
+# description="Open image",
+# maxlen=1024,
+# subtype='FILE_PATH',
+# )
+
+# def invoke(self, context, event):
+# context.window_manager.fileselect_add(self)
+# return {'RUNNING_MODAL'}
+
+# def execute(self, context):
+# im=bpy.data.images.load(self.filepath)
+# tex = context.texture
+# tex.pov.image = im.name
+# view_layer = context.view_layer
+# view_layer.update()
+# return {'FINISHED'}
+
+
+classes = (
+    NODE_MT_POV_map_create,
+    NODE_OT_iso_add,
+    NODE_OT_map_create,
+    NODE_OT_povray_node_texture_map_add,
+    NODE_OT_povray_node_output_add,
+    NODE_OT_povray_node_layered_add,
+    NODE_OT_povray_input_add,
+    NODE_OT_povray_input_remove,
+    NODE_OT_povray_image_open,
+)
+
+
+def register():
+    bpy.types.NODE_HT_header.append(menu_func_nodes)
+    for cls in classes:
+        register_class(cls)
+
+
+def unregister():
+    for cls in reversed(classes):
+        unregister_class(cls)
+    bpy.types.NODE_HT_header.remove(menu_func_nodes)
diff --git a/render_povray/nodes_properties.py b/render_povray/nodes_properties.py
new file mode 100644
index 000000000..8fcc8a3f2
--- /dev/null
+++ b/render_povray/nodes_properties.py
@@ -0,0 +1,703 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+""""Nodes based User interface for shaders exported to POV textures."""
+import bpy
+
+from bpy.utils import register_class, unregister_class
+from bpy.types import NodeSocket, Operator
+from bpy.props import (
+    StringProperty,
+    FloatVectorProperty,
+)
+import nodeitems_utils
+from nodeitems_utils import NodeCategory, NodeItem
+
+# ---------------------------------------------------------------- #
+# Pov Nodes init
+# ---------------------------------------------------------------- #
+# -------- Parent node class
+class ObjectNodeTree(bpy.types.NodeTree):
+    """Povray Material Nodes"""
+
+    bl_idname = "ObjectNodeTree"
+    bl_label = "Povray Object Nodes"
+    bl_icon = "PLUGIN"
+
+    @classmethod
+    def poll(cls, context):
+        return context.scene.render.engine == "POVRAY_RENDER"
+
+    @classmethod
+    def get_from_context(cls, context):
+        ob = context.active_object
+        if ob and ob.type != 'LIGHT':
+            ma = ob.active_material
+            if ma is not None:
+                nt_name = ma.node_tree
+                if nt_name != "":
+                    return nt_name, ma, ma
+        return (None, None, None)
+
+    def update(self):
+        self.refresh = True
+
+
+# -------- Sockets classes
+class PovraySocketUniversal(NodeSocket):
+    bl_idname = "PovraySocketUniversal"
+    bl_label = "Povray Socket"
+    value_unlimited: bpy.props.FloatProperty(default=0.0)
+    value_0_1: bpy.props.FloatProperty(min=0.0, max=1.0, default=0.0)
+    value_0_10: bpy.props.FloatProperty(min=0.0, max=10.0, default=0.0)
+    value_000001_10: bpy.props.FloatProperty(min=0.000001, max=10.0, default=0.0)
+    value_1_9: bpy.props.IntProperty(min=1, max=9, default=1)
+    value_0_255: bpy.props.IntProperty(min=0, max=255, default=0)
+    percent: bpy.props.FloatProperty(min=0.0, max=100.0, default=0.0)
+
+    def draw(self, context, layout, node, text):
+        space = context.space_data
+        tree = space.edit_tree
+        links = tree.links
+        if self.is_linked:
+            value = []
+            for link in links:
+                # inps = link.to_node.inputs
+                linked_inps = (
+                    inp for inp in link.to_node.inputs if link.from_node == node and inp.is_linked
+                )
+                for inp in linked_inps:
+                    if inp.bl_idname == "PovraySocketFloat_0_1":
+                        if (prop := "value_0_1") not in value:
+                            value.append(prop)
+                    if inp.bl_idname == "PovraySocketFloat_000001_10":
+                        if (prop := "value_000001_10") not in value:
+                            value.append(prop)
+                    if inp.bl_idname == "PovraySocketFloat_0_10":
+                        if (prop := "value_0_10") not in value:
+                            value.append(prop)
+                    if inp.bl_idname == "PovraySocketInt_1_9":
+                        if (prop := "value_1_9") not in value:
+                            value.append(prop)
+                    if inp.bl_idname == "PovraySocketInt_0_255":
+                        if (prop := "value_0_255") not in value:
+                            value.append(prop)
+                    if inp.bl_idname == "PovraySocketFloatUnlimited":
+                        if (prop := "value_unlimited") not in value:
+                            value.append(prop)
+            if len(value) == 1:
+                layout.prop(self, "%s" % value[0], text=text)
+            else:
+                layout.prop(self, "percent", text="Percent")
+        else:
+            layout.prop(self, "percent", text=text)
+
+    def draw_color(self, context, node):
+        return (1, 0, 0, 1)
+
+
+class PovraySocketFloat_0_1(NodeSocket):
+    bl_idname = "PovraySocketFloat_0_1"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(
+        description="Input node Value_0_1", min=0, max=1, default=0
+    )
+
+    def draw(self, context, layout, node, text):
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text, slider=True)
+
+    def draw_color(self, context, node):
+        return (0.5, 0.7, 0.7, 1)
+
+
+class PovraySocketFloat_0_10(NodeSocket):
+    bl_idname = "PovraySocketFloat_0_10"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(
+        description="Input node Value_0_10", min=0, max=10, default=0
+    )
+
+    def draw(self, context, layout, node, text):
+        if node.bl_idname == "ShaderNormalMapNode" and node.inputs[2].is_linked:
+            layout.label(text="")
+            self.hide_value = True
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text, slider=True)
+
+    def draw_color(self, context, node):
+        return (0.65, 0.65, 0.65, 1)
+
+
+class PovraySocketFloat_10(NodeSocket):
+    bl_idname = "PovraySocketFloat_10"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(
+        description="Input node Value_10", min=-10, max=10, default=0
+    )
+
+    def draw(self, context, layout, node, text):
+        if node.bl_idname == "ShaderNormalMapNode" and node.inputs[2].is_linked:
+            layout.label(text="")
+            self.hide_value = True
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text, slider=True)
+
+    def draw_color(self, context, node):
+        return (0.65, 0.65, 0.65, 1)
+
+
+class PovraySocketFloatPositive(NodeSocket):
+    bl_idname = "PovraySocketFloatPositive"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(
+        description="Input Node Value Positive", min=0.0, default=0
+    )
+
+    def draw(self, context, layout, node, text):
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text, slider=True)
+
+    def draw_color(self, context, node):
+        return (0.045, 0.005, 0.136, 1)
+
+
+class PovraySocketFloat_000001_10(NodeSocket):
+    bl_idname = "PovraySocketFloat_000001_10"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(min=0.000001, max=10, default=0.000001)
+
+    def draw(self, context, layout, node, text):
+        if self.is_output or self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text, slider=True)
+
+    def draw_color(self, context, node):
+        return (1, 0, 0, 1)
+
+
+class PovraySocketFloatUnlimited(NodeSocket):
+    bl_idname = "PovraySocketFloatUnlimited"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(default=0.0)
+
+    def draw(self, context, layout, node, text):
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text, slider=True)
+
+    def draw_color(self, context, node):
+        return (0.7, 0.7, 1, 1)
+
+
+class PovraySocketInt_1_9(NodeSocket):
+    bl_idname = "PovraySocketInt_1_9"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.IntProperty(
+        description="Input node Value_1_9", min=1, max=9, default=6
+    )
+
+    def draw(self, context, layout, node, text):
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text)
+
+    def draw_color(self, context, node):
+        return (1, 0.7, 0.7, 1)
+
+
+class PovraySocketInt_0_256(NodeSocket):
+    bl_idname = "PovraySocketInt_0_256"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.IntProperty(min=0, max=255, default=0)
+
+    def draw(self, context, layout, node, text):
+        if self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text)
+
+    def draw_color(self, context, node):
+        return (0.5, 0.5, 0.5, 1)
+
+
+class PovraySocketPattern(NodeSocket):
+    bl_idname = "PovraySocketPattern"
+    bl_label = "Povray Socket"
+
+    default_value: bpy.props.EnumProperty(
+        name="Pattern",
+        description="Select the pattern",
+        items=(
+            ("boxed", "Boxed", ""),
+            ("brick", "Brick", ""),
+            ("cells", "Cells", ""),
+            ("checker", "Checker", ""),
+            ("granite", "Granite", ""),
+            ("leopard", "Leopard", ""),
+            ("marble", "Marble", ""),
+            ("onion", "Onion", ""),
+            ("planar", "Planar", ""),
+            ("quilted", "Quilted", ""),
+            ("ripples", "Ripples", ""),
+            ("radial", "Radial", ""),
+            ("spherical", "Spherical", ""),
+            ("spotted", "Spotted", ""),
+            ("waves", "Waves", ""),
+            ("wood", "Wood", ""),
+            ("wrinkles", "Wrinkles", ""),
+        ),
+        default="granite",
+    )
+
+    def draw(self, context, layout, node, text):
+        if self.is_output or self.is_linked:
+            layout.label(text="Pattern")
+        else:
+            layout.prop(self, "default_value", text=text)
+
+    def draw_color(self, context, node):
+        return (1, 1, 1, 1)
+
+
+class PovraySocketColor(NodeSocket):
+    bl_idname = "PovraySocketColor"
+    bl_label = "Povray Socket"
+
+    default_value: FloatVectorProperty(
+        precision=4,
+        step=0.01,
+        min=0,
+        soft_max=1,
+        default=(0.0, 0.0, 0.0),
+        options={"ANIMATABLE"},
+        subtype="COLOR",
+    )
+
+    def draw(self, context, layout, node, text):
+        if self.is_output or self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text)
+
+    def draw_color(self, context, node):
+        return (1, 1, 0, 1)
+
+
+class PovraySocketColorRGBFT(NodeSocket):
+    bl_idname = "PovraySocketColorRGBFT"
+    bl_label = "Povray Socket"
+
+    default_value: FloatVectorProperty(
+        precision=4,
+        step=0.01,
+        min=0,
+        soft_max=1,
+        default=(0.0, 0.0, 0.0),
+        options={"ANIMATABLE"},
+        subtype="COLOR",
+    )
+    f: bpy.props.FloatProperty(default=0.0, min=0.0, max=1.0)
+    t: bpy.props.FloatProperty(default=0.0, min=0.0, max=1.0)
+
+    def draw(self, context, layout, node, text):
+        if self.is_output or self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text=text)
+
+    def draw_color(self, context, node):
+        return (1, 1, 0, 1)
+
+
+class PovraySocketTexture(NodeSocket):
+    bl_idname = "PovraySocketTexture"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.IntProperty()
+
+    def draw(self, context, layout, node, text):
+        layout.label(text=text)
+
+    def draw_color(self, context, node):
+        return (0, 1, 0, 1)
+
+
+class PovraySocketTransform(NodeSocket):
+    bl_idname = "PovraySocketTransform"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.IntProperty(min=0, max=255, default=0)
+
+    def draw(self, context, layout, node, text):
+        layout.label(text=text)
+
+    def draw_color(self, context, node):
+        return (99 / 255, 99 / 255, 199 / 255, 1)
+
+
+class PovraySocketNormal(NodeSocket):
+    bl_idname = "PovraySocketNormal"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.IntProperty(min=0, max=255, default=0)
+
+    def draw(self, context, layout, node, text):
+        layout.label(text=text)
+
+    def draw_color(self, context, node):
+        return (0.65, 0.65, 0.65, 1)
+
+
+class PovraySocketSlope(NodeSocket):
+    bl_idname = "PovraySocketSlope"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.FloatProperty(min=0.0, max=1.0)
+    height: bpy.props.FloatProperty(min=0.0, max=10.0)
+    slope: bpy.props.FloatProperty(min=-10.0, max=10.0)
+
+    def draw(self, context, layout, node, text):
+        if self.is_output or self.is_linked:
+            layout.label(text=text)
+        else:
+            layout.prop(self, "default_value", text="")
+            layout.prop(self, "height", text="")
+            layout.prop(self, "slope", text="")
+
+    def draw_color(self, context, node):
+        return (0, 0, 0, 1)
+
+
+class PovraySocketMap(NodeSocket):
+    bl_idname = "PovraySocketMap"
+    bl_label = "Povray Socket"
+    default_value: bpy.props.StringProperty()
+
+    def draw(self, context, layout, node, text):
+        layout.label(text=text)
+
+    def draw_color(self, context, node):
+        return (0.2, 0, 0.2, 1)
+
+
+class PovrayPatternNode(Operator):
+    bl_idname = "pov.patternnode"
+    bl_label = "Pattern"
+
+    add = True
+
+    def execute(self, context):
+        space = context.space_data
+        tree = space.edit_tree
+        for node in tree.nodes:
+            node.select = False
+        if self.add:
+            tmap = tree.nodes.new("ShaderNodeValToRGB")
+            tmap.label = "Pattern"
+            for inp in tmap.inputs:
+                tmap.inputs.remove(inp)
+            for outp in tmap.outputs:
+                tmap.outputs.remove(outp)
+            pattern = tmap.inputs.new("PovraySocketPattern", "Pattern")
+            pattern.hide_value = True
+            mapping = tmap.inputs.new("NodeSocketVector", "Mapping")
+            mapping.hide_value = True
+            transform = tmap.inputs.new("NodeSocketVector", "Transform")
+            transform.hide_value = True
+            modifier = tmap.inputs.new("NodeSocketVector", "Modifier")
+            modifier.hide_value = True
+            for i in range(0, 2):
+                tmap.inputs.new("NodeSocketShader", "%s" % (i + 1))
+            tmap.outputs.new("NodeSocketShader", "Material")
+            tmap.outputs.new("NodeSocketColor", "Color")
+            tree.nodes.active = tmap
+            self.add = False
+        aNode = tree.nodes.active
+        aNode.select = True
+        v2d = context.region.view2d
+        x, y = v2d.region_to_view(self.x, self.y)
+        aNode.location = (x, y)
+
+    def modal(self, context, event):
+        if event.type == "MOUSEMOVE":
+            self.x = event.mouse_region_x
+            self.y = event.mouse_region_y
+            self.execute(context)
+            return {"RUNNING_MODAL"}
+        if event.type == "LEFTMOUSE":
+            return {"FINISHED"}
+        if event.type in ("RIGHTMOUSE", "ESC"):
+            return {"CANCELLED"}
+
+        return {"RUNNING_MODAL"}
+
+    def invoke(self, context, event):
+        context.window_manager.modal_handler_add(self)
+        return {"RUNNING_MODAL"}
+
+
+class UpdatePreviewMaterial(Operator):
+    """Operator update preview material"""
+
+    bl_idname = "node.updatepreview"
+    bl_label = "Update preview"
+
+    def execute(self, context):
+        scene = context.view_layer
+        ob = context.object
+        for obj in scene.objects:
+            if obj != ob:
+                scene.objects.active = ob
+                break
+        scene.objects.active = ob
+
+    def modal(self, context, event):
+        if event.type == "RIGHTMOUSE":
+            self.execute(context)
+            return {"FINISHED"}
+        return {"PASS_THROUGH"}
+
+    def invoke(self, context, event):
+        context.window_manager.modal_handler_add(self)
+        return {"RUNNING_MODAL"}
+
+
+class UpdatePreviewKey(Operator):
+    """Operator update preview keymap"""
+
+    bl_idname = "wm.updatepreviewkey"
+    bl_label = "Activate RMB"
+
+    @classmethod
+    def poll(cls, context):
+        conf = context.window_manager.keyconfigs.active
+        mapstr = "Node Editor"
+        map = conf.keymaps[mapstr]
+        try:
+            map.keymap_items["node.updatepreview"]
+            return False
+        except BaseException as e:
+            print(e.__doc__)
+            print("An exception occurred: {}".format(e))
+            return True
+
+    def execute(self, context):
+        conf = context.window_manager.keyconfigs.active
+        mapstr = "Node Editor"
+        map = conf.keymaps[mapstr]
+        map.keymap_items.new("node.updatepreview", type="RIGHTMOUSE", value="PRESS")
+        return {"FINISHED"}
+
+
+class PovrayShaderNodeCategory(NodeCategory):
+    @classmethod
+    def poll(cls, context):
+        return context.space_data.tree_type == "ObjectNodeTree"
+
+
+class PovrayTextureNodeCategory(NodeCategory):
+    @classmethod
+    def poll(cls, context):
+        return context.space_data.tree_type == "TextureNodeTree"
+
+
+class PovraySceneNodeCategory(NodeCategory):
+    @classmethod
+    def poll(cls, context):
+        return context.space_data.tree_type == "CompositorNodeTree"
+
+
+node_categories = [
+    PovrayShaderNodeCategory("SHADEROUTPUT", "Output", items=[NodeItem("PovrayOutputNode")]),
+    PovrayShaderNodeCategory("SIMPLE", "Simple texture", items=[NodeItem("PovrayTextureNode")]),
+    PovrayShaderNodeCategory(
+        "MAPS",
+        "Maps",
+        items=[
+            NodeItem("PovrayBumpMapNode"),
+            NodeItem("PovrayColorImageNode"),
+            NodeItem("ShaderNormalMapNode"),
+            NodeItem("PovraySlopeNode"),
+            NodeItem("ShaderTextureMapNode"),
+            NodeItem("ShaderNodeValToRGB"),
+        ],
+    ),
+    PovrayShaderNodeCategory(
+        "OTHER",
+        "Other patterns",
+        items=[NodeItem("PovrayImagePatternNode"), NodeItem("ShaderPatternNode")],
+    ),
+    PovrayShaderNodeCategory("COLOR", "Color", items=[NodeItem("PovrayPigmentNode")]),
+    PovrayShaderNodeCategory(
+        "TRANSFORM",
+        "Transform",
+        items=[
+            NodeItem("PovrayMappingNode"),
+            NodeItem("PovrayMultiplyNode"),
+            NodeItem("PovrayModifierNode"),
+            NodeItem("PovrayTransformNode"),
+            NodeItem("PovrayValueNode"),
+        ],
+    ),
+    PovrayShaderNodeCategory(
+        "FINISH",
+        "Finish",
+        items=[
+            NodeItem("PovrayFinishNode"),
+            NodeItem("PovrayDiffuseNode"),
+            NodeItem("PovraySpecularNode"),
+            NodeItem("PovrayPhongNode"),
+            NodeItem("PovrayAmbientNode"),
+            NodeItem("PovrayMirrorNode"),
+            NodeItem("PovrayIridescenceNode"),
+            NodeItem("PovraySubsurfaceNode"),
+        ],
+    ),
+    PovrayShaderNodeCategory(
+        "CYCLES",
+        "Cycles",
+        items=[
+            NodeItem("ShaderNodeAddShader"),
+            NodeItem("ShaderNodeAmbientOcclusion"),
+            NodeItem("ShaderNodeAttribute"),
+            NodeItem("ShaderNodeBackground"),
+            NodeItem("ShaderNodeBlackbody"),
+            NodeItem("ShaderNodeBrightContrast"),
+            NodeItem("ShaderNodeBsdfAnisotropic"),
+            NodeItem("ShaderNodeBsdfDiffuse"),
+            NodeItem("ShaderNodeBsdfGlass"),
+            NodeItem("ShaderNodeBsdfGlossy"),
+            NodeItem("ShaderNodeBsdfHair"),
+            NodeItem("ShaderNodeBsdfRefraction"),
+            NodeItem("ShaderNodeBsdfToon"),
+            NodeItem("ShaderNodeBsdfTranslucent"),
+            NodeItem("ShaderNodeBsdfTransparent"),
+            NodeItem("ShaderNodeBsdfVelvet"),
+            NodeItem("ShaderNodeBump"),
+            NodeItem("ShaderNodeCameraData"),
+            NodeItem("ShaderNodeCombineHSV"),
+            NodeItem("ShaderNodeCombineRGB"),
+            NodeItem("ShaderNodeCombineXYZ"),
+            NodeItem("ShaderNodeEmission"),
+            NodeItem("ShaderNodeExtendedMaterial"),
+            NodeItem("ShaderNodeFresnel"),
+            NodeItem("ShaderNodeGamma"),
+            NodeItem("ShaderNodeGeometry"),
+            NodeItem("ShaderNodeGroup"),
+            NodeItem("ShaderNodeHairInfo"),
+            NodeItem("ShaderNodeHoldout"),
+            NodeItem("ShaderNodeHueSaturation"),
+            NodeItem("ShaderNodeInvert"),
+            NodeItem("ShaderNodeLampData"),
+            NodeItem("ShaderNodeLayerWeight"),
+            NodeItem("ShaderNodeLightFalloff"),
+            NodeItem("ShaderNodeLightPath"),
+            NodeItem("ShaderNodeMapping"),
+            NodeItem("ShaderNodeMaterial"),
+            NodeItem("ShaderNodeMath"),
+            NodeItem("ShaderNodeMixRGB"),
+            NodeItem("ShaderNodeMixShader"),
+            NodeItem("ShaderNodeNewGeometry"),
+            NodeItem("ShaderNodeNormal"),
+            NodeItem("ShaderNodeNormalMap"),
+            NodeItem("ShaderNodeObjectInfo"),
+            NodeItem("ShaderNodeOutput"),
+            NodeItem("ShaderNodeOutputLamp"),
+            NodeItem("ShaderNodeOutputLineStyle"),
+            NodeItem("ShaderNodeOutputMaterial"),
+            NodeItem("ShaderNodeOutputWorld"),
+            NodeItem("ShaderNodeParticleInfo"),
+            NodeItem("ShaderNodeRGB"),
+            NodeItem("ShaderNodeRGBCurve"),
+            NodeItem("ShaderNodeRGBToBW"),
+            NodeItem("ShaderNodeScript"),
+            NodeItem("ShaderNodeSeparateHSV"),
+            NodeItem("ShaderNodeSeparateRGB"),
+            NodeItem("ShaderNodeSeparateXYZ"),
+            NodeItem("ShaderNodeSqueeze"),
+            NodeItem("ShaderNodeSubsurfaceScattering"),
+            NodeItem("ShaderNodeTangent"),
+            NodeItem("ShaderNodeTexBrick"),
+            NodeItem("ShaderNodeTexChecker"),
+            NodeItem("ShaderNodeTexCoord"),
+            NodeItem("ShaderNodeTexEnvironment"),
+            NodeItem("ShaderNodeTexGradient"),
+            NodeItem("ShaderNodeTexImage"),
+            NodeItem("ShaderNodeTexMagic"),
+            NodeItem("ShaderNodeTexMusgrave"),
+            NodeItem("ShaderNodeTexNoise"),
+            NodeItem("ShaderNodeTexPointDensity"),
+            NodeItem("ShaderNodeTexSky"),
+            NodeItem("ShaderNodeTexVoronoi"),
+            NodeItem("ShaderNodeTexWave"),
+            NodeItem("ShaderNodeTexture"),
+            NodeItem("ShaderNodeUVAlongStroke"),
+            NodeItem("ShaderNodeUVMap"),
+            NodeItem("ShaderNodeValToRGB"),
+            NodeItem("ShaderNodeValue"),
+            NodeItem("ShaderNodeVectorCurve"),
+            NodeItem("ShaderNodeVectorMath"),
+            NodeItem("ShaderNodeVectorTransform"),
+            NodeItem("ShaderNodeVolumeAbsorption"),
+            NodeItem("ShaderNodeVolumeScatter"),
+            NodeItem("ShaderNodeWavelength"),
+            NodeItem("ShaderNodeWireframe"),
+        ],
+    ),
+    PovrayTextureNodeCategory(
+        "TEXTUREOUTPUT",
+        "Output",
+        items=[NodeItem("TextureNodeValToRGB"), NodeItem("TextureOutputNode")],
+    ),
+    PovraySceneNodeCategory("ISOSURFACE", "Isosurface", items=[NodeItem("IsoPropsNode")]),
+    PovraySceneNodeCategory("FOG", "Fog", items=[NodeItem("PovrayFogNode")]),
+]
+# -------- end nodes init
+
+
+classes = (
+    ObjectNodeTree,
+    PovraySocketUniversal,
+    PovraySocketFloat_0_1,
+    PovraySocketFloat_0_10,
+    PovraySocketFloat_10,
+    PovraySocketFloatPositive,
+    PovraySocketFloat_000001_10,
+    PovraySocketFloatUnlimited,
+    PovraySocketInt_1_9,
+    PovraySocketInt_0_256,
+    PovraySocketPattern,
+    PovraySocketColor,
+    PovraySocketColorRGBFT,
+    PovraySocketTexture,
+    PovraySocketTransform,
+    PovraySocketNormal,
+    PovraySocketSlope,
+    PovraySocketMap,
+    # PovrayShaderNodeCategory, # XXX SOMETHING BROKEN from 2.8 ?
+    # PovrayTextureNodeCategory, # XXX SOMETHING BROKEN from 2.8 ?
+    # PovraySceneNodeCategory, # XXX SOMETHING BROKEN from 2.8 ?
+    PovrayPatternNode,
+    UpdatePreviewMaterial,
+    UpdatePreviewKey,
+)
+
+
+def register():
+    nodeitems_utils.register_node_categories("POVRAYNODES", node_categories)
+    for cls in classes:
+        register_class(cls)
+
+
+def unregister():
+    for cls in reversed(classes):
+        unregister_class(cls)
+    nodeitems_utils.unregister_node_categories("POVRAYNODES")
diff --git a/render_povray/object_curve_topology.py b/render_povray/object_curve_topology.py
deleted file mode 100755
index 2be6c3ded..000000000
--- a/render_povray/object_curve_topology.py
+++ /dev/null
@@ -1,971 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-# <pep8 compliant>
-
-"""Translate to POV the control point compounded geometries like polygon
-
-meshes or curve based shapes.
-"""
-
-import bpy
-
-from .shading import write_object_material_interior
-
-# -------- LOFT, ETC.
-
-
-def export_curves(file, ob, string_strip_hyphen, tab_write):
-    """write all curves based POV primitives to exported file
-
-    Args:
-        file: The POV file being written
-        ob: The current curve object to export from Blender
-        string_strip_hyphen: Function to clean up names
-        tab_write: Function to write to POV file
-    """
-    # name_orig = "OB" + ob.name # XXX Unused, check instantiation
-    dataname_orig = "DATA" + ob.data.name
-
-    # name = string_strip_hyphen(bpy.path.clean_name(name_orig)) # XXX Unused, check instantiation
-    dataname = string_strip_hyphen(bpy.path.clean_name(dataname_orig))
-
-    # matrix = global_matrix @ ob.matrix_world # XXX Unused, check instantiation
-    bezier_sweep = False
-    if ob.pov.curveshape == 'sphere_sweep':
-        # TODO: Check radius ; shorten lines, may use tab_write() ? > fstrings since py 2.9
-        # inlined spheresweep macro, which itself calls Shapes.inc:
-        file.write('        #include "shapes.inc"\n')
-
-        file.write(
-            '        #macro Shape_Bezierpoints_Sphere_Sweep(_merge_shape, _resolution, _points_array, _radius_array)\n'
-        )
-        file.write('        //input adjusting and inspection\n')
-        file.write('        #if(_resolution <= 1)\n')
-        file.write('            #local res = 1;\n')
-        file.write('        #else\n')
-        file.write('            #local res = int(_resolution);\n')
-        file.write('        #end\n')
-        file.write('        #if(dimensions(_points_array) != 1 | dimensions(_radius_array) != 1)\n')
-        file.write('            #error ""\n')
-        file.write(
-            '        #elseif(div(dimension_size(_points_array,1),4) - dimension_size(_points_array,1)/4 != 0)\n'
-        )
-        file.write('            #error ""\n')
-        file.write(
-            '        #elseif(dimension_size(_points_array,1) != dimension_size(_radius_array,1))\n'
-        )
-        file.write('            #error ""\n')
-        file.write('        #else\n')
-        file.write('            #local n_of_seg = div(dimension_size(_points_array,1), 4);\n')
-        file.write('            #local ctrl_pts_array = array[n_of_seg]\n')
-        file.write('            #local ctrl_rs_array = array[n_of_seg]\n')
-        file.write('            #for(i, 0, n_of_seg-1)\n')
-        file.write(
-            '                #local ctrl_pts_array[i] = array[4] {_points_array[4*i], _points_array[4*i+1], _points_array[4*i+2], _points_array[4*i+3]}\n'
-        )
-        file.write(
-            '                #local ctrl_rs_array[i] = array[4] {abs(_radius_array[4*i]), abs(_radius_array[4*i+1]), abs(_radius_array[4*i+2]), abs(_radius_array[4*i+3])}\n'
-        )
-        file.write('            #end\n')
-        file.write('        #end\n')
-
-        file.write('        //drawing\n')
-        file.write('        #local mockup1 =\n')
-        file.write('        #if(_merge_shape) merge{ #else union{ #end\n')
-        file.write('            #for(i, 0, n_of_seg-1)\n')
-        file.write('                #local has_head = true;\n')
-        file.write('                #if(i = 0)\n')
-        file.write(
-            '                    #if(vlength(ctrl_pts_array[i][0]-ctrl_pts_array[n_of_seg-1][3]) = 0 & ctrl_rs_array[i][0]-ctrl_rs_array[n_of_seg-1][3] <= 0)\n'
-        )
-        file.write('                        #local has_head = false;\n')
-        file.write('                    #end\n')
-        file.write('                #else\n')
-        file.write(
-            '                    #if(vlength(ctrl_pts_array[i][0]-ctrl_pts_array[i-1][3]) = 0 & ctrl_rs_array[i][0]-ctrl_rs_array[i-1][3] <= 0)\n'
-        )
-        file.write('                        #local has_head = false;\n')
-        file.write('                    #end\n')
-        file.write('                #end\n')
-        file.write('                #if(has_head = true)\n')
-        file.write('                    sphere{\n')
-        file.write('                    ctrl_pts_array[i][0], ctrl_rs_array[i][0]\n')
-        file.write('                    }\n')
-        file.write('                #end\n')
-        file.write('                #local para_t = (1/2)/res;\n')
-        file.write(
-            '                #local this_point = ctrl_pts_array[i][0]*pow(1-para_t,3) + ctrl_pts_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_pts_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_pts_array[i][3]*pow(para_t,3);\n'
-        )
-        file.write(
-            '                #local this_radius = ctrl_rs_array[i][0]*pow(1-para_t,3) + ctrl_rs_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_rs_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_rs_array[i][3]*pow(para_t,3);\n'
-        )
-        file.write(
-            '                #if(vlength(this_point-ctrl_pts_array[i][0]) > abs(this_radius-ctrl_rs_array[i][0]))\n'
-        )
-        file.write('                    object{\n')
-        file.write(
-            '                    Connect_Spheres(ctrl_pts_array[i][0], ctrl_rs_array[i][0], this_point, this_radius)\n'
-        )
-        file.write('                    }\n')
-        file.write('                #end\n')
-        file.write('                sphere{\n')
-        file.write('                this_point, this_radius\n')
-        file.write('                }\n')
-        file.write('                #for(j, 1, res-1)\n')
-        file.write('                    #local last_point = this_point;\n')
-        file.write('                    #local last_radius = this_radius;\n')
-        file.write('                    #local para_t = (1/2+j)/res;\n')
-        file.write(
-            '                    #local this_point = ctrl_pts_array[i][0]*pow(1-para_t,3) + ctrl_pts_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_pts_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_pts_array[i][3]*pow(para_t,3);\n'
-        )
-        file.write(
-            '                    #local this_radius = ctrl_rs_array[i][0]*pow(1-para_t,3) + ctrl_rs_array[i][1]*3*pow(1-para_t,2)*para_t + ctrl_rs_array[i][2]*3*(1-para_t)*pow(para_t,2) + ctrl_rs_array[i][3]*pow(para_t,3);\n'
-        )
-        file.write(
-            '                    #if(vlength(this_point-last_point) > abs(this_radius-last_radius))\n'
-        )
-        file.write('                        object{\n')
-        file.write(
-            '                        Connect_Spheres(last_point, last_radius, this_point, this_radius)\n'
-        )
-        file.write('                        }\n')
-        file.write('                    #end\n')
-        file.write('                    sphere{\n')
-        file.write('                    this_point, this_radius\n')
-        file.write('                    }\n')
-        file.write('                #end\n')
-        file.write('                #local last_point = this_point;\n')
-        file.write('                #local last_radius = this_radius;\n')
-        file.write('                #local this_point = ctrl_pts_array[i][3];\n')
-        file.write('                #local this_radius = ctrl_rs_array[i][3];\n')
-        file.write(
-            '                #if(vlength(this_point-last_point) > abs(this_radius-last_radius))\n'
-        )
-        file.write('                    object{\n')
-        file.write(
-            '                    Connect_Spheres(last_point, last_radius, this_point, this_radius)\n'
-        )
-        file.write('                    }\n')
-        file.write('                #end\n')
-        file.write('                sphere{\n')
-        file.write('                this_point, this_radius\n')
-        file.write('                }\n')
-        file.write('            #end\n')
-        file.write('        }\n')
-        file.write('        mockup1\n')
-        file.write('        #end\n')
-
-        for spl in ob.data.splines:
-            if spl.type == "BEZIER":
-                bezier_sweep = True
-    if ob.pov.curveshape in {'loft', 'birail'}:
-        n = 0
-        for spline in ob.data.splines:
-            n += 1
-            tab_write('#declare %s%s=spline {\n' % (dataname, n))
-            tab_write('cubic_spline\n')
-            lp = len(spline.points)
-            delta = 1 / lp
-            d = -delta
-            point = spline.points[lp - 1]
-            x, y, z, w = point.co[:]
-            tab_write('%.6f, <%.6f,%.6f,%.6f>\n' % (d, x, y, z))
-            d += delta
-            for point in spline.points:
-                x, y, z, w = point.co[:]
-                tab_write('%.6f, <%.6f,%.6f,%.6f>\n' % (d, x, y, z))
-                d += delta
-            for i in range(2):
-                point = spline.points[i]
-                x, y, z, w = point.co[:]
-                tab_write('%.6f, <%.6f,%.6f,%.6f>\n' % (d, x, y, z))
-                d += delta
-            tab_write('}\n')
-        if ob.pov.curveshape in {'loft'}:
-            n = len(ob.data.splines)
-            tab_write('#declare %s = array[%s]{\n' % (dataname, (n + 3)))
-            tab_write('spline{%s%s},\n' % (dataname, n))
-            for i in range(n):
-                tab_write('spline{%s%s},\n' % (dataname, (i + 1)))
-            tab_write('spline{%s1},\n' % dataname)
-            tab_write('spline{%s2}\n' % dataname)
-            tab_write('}\n')
-        # Use some of the Meshmaker.inc macro, here inlined
-        file.write('#macro CheckFileName(FileName)\n')
-        file.write('   #local Len=strlen(FileName);\n')
-        file.write('   #if(Len>0)\n')
-        file.write('      #if(file_exists(FileName))\n')
-        file.write('         #if(Len>=4)\n')
-        file.write('            #local Ext=strlwr(substr(FileName,Len-3,4))\n')
-        file.write(
-            '            #if (strcmp(Ext,".obj")=0 | strcmp(Ext,".pcm")=0 | strcmp(Ext,".arr")=0)\n'
-        )
-        file.write('               #local Return=99;\n')
-        file.write('            #else\n')
-        file.write('               #local Return=0;\n')
-        file.write('            #end\n')
-        file.write('         #else\n')
-        file.write('            #local Return=0;\n')
-        file.write('         #end\n')
-        file.write('      #else\n')
-        file.write('         #if(Len>=4)\n')
-        file.write('            #local Ext=strlwr(substr(FileName,Len-3,4))\n')
-        file.write(
-            '            #if (strcmp(Ext,".obj")=0 | strcmp(Ext,".pcm")=0 | strcmp(Ext,".arr")=0)\n'
-        )
-        file.write('               #if (strcmp(Ext,".obj")=0)\n')
-        file.write('                  #local Return=2;\n')
-        file.write('               #end\n')
-        file.write('               #if (strcmp(Ext,".pcm")=0)\n')
-        file.write('                  #local Return=3;\n')
-        file.write('               #end\n')
-        file.write('               #if (strcmp(Ext,".arr")=0)\n')
-        file.write('                  #local Return=4;\n')
-        file.write('               #end\n')
-        file.write('            #else\n')
-        file.write('               #local Return=1;\n')
-        file.write('            #end\n')
-        file.write('         #else\n')
-        file.write('            #local Return=1;\n')
-        file.write('         #end\n')
-        file.write('      #end\n')
-        file.write('   #else\n')
-        file.write('      #local Return=1;\n')
-        file.write('   #end\n')
-        file.write('   (Return)\n')
-        file.write('#end\n')
-
-        file.write('#macro BuildSpline(Arr, SplType)\n')
-        file.write('   #local Ds=dimension_size(Arr,1);\n')
-        file.write('   #local Asc=asc(strupr(SplType));\n')
-        file.write('   #if(Asc!=67 & Asc!=76 & Asc!=81) \n')
-        file.write('      #local Asc=76;\n')
-        file.write(
-            '      #debug "\nWrong spline type defined (C/c/L/l/N/n/Q/q), using default linear_spline\\n"\n'
-        )
-        file.write('   #end\n')
-        file.write('   spline {\n')
-        file.write('      #switch (Asc)\n')
-        file.write('         #case (67) //C  cubic_spline\n')
-        file.write('            cubic_spline\n')
-        file.write('         #break\n')
-        file.write('         #case (76) //L  linear_spline\n')
-        file.write('            linear_spline\n')
-        file.write('         #break\n')
-        file.write('         #case (78) //N  linear_spline\n')
-        file.write('            natural_spline\n')
-        file.write('         #break\n')
-        file.write('         #case (81) //Q  Quadratic_spline\n')
-        file.write('            quadratic_spline\n')
-        file.write('         #break\n')
-        file.write('      #end\n')
-        file.write('      #local Add=1/((Ds-2)-1);\n')
-        file.write('      #local J=0-Add;\n')
-        file.write('      #local I=0;\n')
-        file.write('      #while (I<Ds)\n')
-        file.write('         J\n')
-        file.write('         Arr[I]\n')
-        file.write('         #local I=I+1;\n')
-        file.write('         #local J=J+Add;\n')
-        file.write('      #end\n')
-        file.write('   }\n')
-        file.write('#end\n')
-
-        file.write('#macro BuildWriteMesh2(VecArr, NormArr, UVArr, U, V, FileName)\n')
-        # suppressed some file checking from original macro because no more separate files
-        file.write(' #local Write=0;\n')
-        file.write(' #debug concat("\\n\\n Building mesh2: \\n   - vertex_vectors\\n")\n')
-        file.write('  #local NumVertices=dimension_size(VecArr,1);\n')
-        file.write('  #switch (Write)\n')
-        file.write('     #case(1)\n')
-        file.write('        #write(\n')
-        file.write('           MeshFile,\n')
-        file.write('           "  vertex_vectors {\\n",\n')
-        file.write('           "    ", str(NumVertices,0,0),"\\n    "\n')
-        file.write('        )\n')
-        file.write('     #break\n')
-        file.write('     #case(2)\n')
-        file.write('        #write(\n')
-        file.write('           MeshFile,\n')
-        file.write('           "# Vertices: ",str(NumVertices,0,0),"\\n"\n')
-        file.write('        )\n')
-        file.write('     #break\n')
-        file.write('     #case(3)\n')
-        file.write('        #write(\n')
-        file.write('           MeshFile,\n')
-        file.write('           str(2*NumVertices,0,0),",\\n"\n')
-        file.write('        )\n')
-        file.write('     #break\n')
-        file.write('     #case(4)\n')
-        file.write('        #write(\n')
-        file.write('           MeshFile,\n')
-        file.write('           "#declare VertexVectors= array[",str(NumVertices,0,0),"] {\\n  "\n')
-        file.write('        )\n')
-        file.write('     #break\n')
-        file.write('  #end\n')
-        file.write('  mesh2 {\n')
-        file.write('     vertex_vectors {\n')
-        file.write('        NumVertices\n')
-        file.write('        #local I=0;\n')
-        file.write('        #while (I<NumVertices)\n')
-        file.write('           VecArr[I]\n')
-        file.write('           #switch(Write)\n')
-        file.write('              #case(1)\n')
-        file.write('                 #write(MeshFile, VecArr[I])\n')
-        file.write('              #break\n')
-        file.write('              #case(2)\n')
-        file.write('                 #write(\n')
-        file.write('                    MeshFile,\n')
-        file.write(
-            '                    "v ", VecArr[I].x," ", VecArr[I].y," ", VecArr[I].z,"\\n"\n'
-        )
-        file.write('                 )\n')
-        file.write('              #break\n')
-        file.write('              #case(3)\n')
-        file.write('                 #write(\n')
-        file.write('                    MeshFile,\n')
-        file.write('                    VecArr[I].x,",", VecArr[I].y,",", VecArr[I].z,",\\n"\n')
-        file.write('                 )\n')
-        file.write('              #break\n')
-        file.write('              #case(4)\n')
-        file.write('                 #write(MeshFile, VecArr[I])\n')
-        file.write('              #break\n')
-        file.write('           #end\n')
-        file.write('           #local I=I+1;\n')
-        file.write('           #if(Write=1 | Write=4)\n')
-        file.write('              #if(mod(I,3)=0)\n')
-        file.write('                 #write(MeshFile,"\\n    ")\n')
-        file.write('              #end\n')
-        file.write('           #end \n')
-        file.write('        #end\n')
-        file.write('        #switch(Write)\n')
-        file.write('           #case(1)\n')
-        file.write('              #write(MeshFile,"\\n  }\\n")\n')
-        file.write('           #break\n')
-        file.write('           #case(2)\n')
-        file.write('              #write(MeshFile,"\\n")\n')
-        file.write('           #break\n')
-        file.write('           #case(3)\n')
-        file.write('              // do nothing\n')
-        file.write('           #break\n')
-        file.write('           #case(4) \n')
-        file.write('              #write(MeshFile,"\\n}\\n")\n')
-        file.write('           #break\n')
-        file.write('        #end\n')
-        file.write('     }\n')
-
-        file.write('     #debug concat("   - normal_vectors\\n")    \n')
-        file.write('     #local NumVertices=dimension_size(NormArr,1);\n')
-        file.write('     #switch(Write)\n')
-        file.write('        #case(1)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile,\n')
-        file.write('              "  normal_vectors {\\n",\n')
-        file.write('              "    ", str(NumVertices,0,0),"\\n    "\n')
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('        #case(2)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile,\n')
-        file.write('              "# Normals: ",str(NumVertices,0,0),"\\n"\n')
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('        #case(3)\n')
-        file.write('           // do nothing\n')
-        file.write('        #break\n')
-        file.write('        #case(4)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile,\n')
-        file.write(
-            '              "#declare NormalVectors= array[",str(NumVertices,0,0),"] {\\n  "\n'
-        )
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('     #end\n')
-        file.write('     normal_vectors {\n')
-        file.write('        NumVertices\n')
-        file.write('        #local I=0;\n')
-        file.write('        #while (I<NumVertices)\n')
-        file.write('           NormArr[I]\n')
-        file.write('           #switch(Write)\n')
-        file.write('              #case(1)\n')
-        file.write('                 #write(MeshFile NormArr[I])\n')
-        file.write('              #break\n')
-        file.write('              #case(2)\n')
-        file.write('                 #write(\n')
-        file.write('                    MeshFile,\n')
-        file.write(
-            '                    "vn ", NormArr[I].x," ", NormArr[I].y," ", NormArr[I].z,"\\n"\n'
-        )
-        file.write('                 )\n')
-        file.write('              #break\n')
-        file.write('              #case(3)\n')
-        file.write('                 #write(\n')
-        file.write('                    MeshFile,\n')
-        file.write('                    NormArr[I].x,",", NormArr[I].y,",", NormArr[I].z,",\\n"\n')
-        file.write('                 )\n')
-        file.write('              #break\n')
-        file.write('              #case(4)\n')
-        file.write('                 #write(MeshFile NormArr[I])\n')
-        file.write('              #break\n')
-        file.write('           #end\n')
-        file.write('           #local I=I+1;\n')
-        file.write('           #if(Write=1 | Write=4) \n')
-        file.write('              #if(mod(I,3)=0)\n')
-        file.write('                 #write(MeshFile,"\\n    ")\n')
-        file.write('              #end\n')
-        file.write('           #end\n')
-        file.write('        #end\n')
-        file.write('        #switch(Write)\n')
-        file.write('           #case(1)\n')
-        file.write('              #write(MeshFile,"\\n  }\\n")\n')
-        file.write('           #break\n')
-        file.write('           #case(2)\n')
-        file.write('              #write(MeshFile,"\\n")\n')
-        file.write('           #break\n')
-        file.write('           #case(3)\n')
-        file.write('              //do nothing\n')
-        file.write('           #break\n')
-        file.write('           #case(4)\n')
-        file.write('              #write(MeshFile,"\\n}\\n")\n')
-        file.write('           #break\n')
-        file.write('        #end\n')
-        file.write('     }\n')
-
-        file.write('     #debug concat("   - uv_vectors\\n")   \n')
-        file.write('     #local NumVertices=dimension_size(UVArr,1);\n')
-        file.write('     #switch(Write)\n')
-        file.write('        #case(1)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile, \n')
-        file.write('              "  uv_vectors {\\n",\n')
-        file.write('              "    ", str(NumVertices,0,0),"\\n    "\n')
-        file.write('           )\n')
-        file.write('         #break\n')
-        file.write('         #case(2)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile,\n')
-        file.write('              "# UV-vectors: ",str(NumVertices,0,0),"\\n"\n')
-        file.write('           )\n')
-        file.write('         #break\n')
-        file.write('         #case(3)\n')
-        file.write('           // do nothing, *.pcm does not support uv-vectors\n')
-        file.write('         #break\n')
-        file.write('         #case(4)\n')
-        file.write('            #write(\n')
-        file.write('               MeshFile,\n')
-        file.write('               "#declare UVVectors= array[",str(NumVertices,0,0),"] {\\n  "\n')
-        file.write('            )\n')
-        file.write('         #break\n')
-        file.write('     #end\n')
-        file.write('     uv_vectors {\n')
-        file.write('        NumVertices\n')
-        file.write('        #local I=0;\n')
-        file.write('        #while (I<NumVertices)\n')
-        file.write('           UVArr[I]\n')
-        file.write('           #switch(Write)\n')
-        file.write('              #case(1)\n')
-        file.write('                 #write(MeshFile UVArr[I])\n')
-        file.write('              #break\n')
-        file.write('              #case(2)\n')
-        file.write('                 #write(\n')
-        file.write('                    MeshFile,\n')
-        file.write('                    "vt ", UVArr[I].u," ", UVArr[I].v,"\\n"\n')
-        file.write('                 )\n')
-        file.write('              #break\n')
-        file.write('              #case(3)\n')
-        file.write('                 //do nothing\n')
-        file.write('              #break\n')
-        file.write('              #case(4)\n')
-        file.write('                 #write(MeshFile UVArr[I])\n')
-        file.write('              #break\n')
-        file.write('           #end\n')
-        file.write('           #local I=I+1; \n')
-        file.write('           #if(Write=1 | Write=4)\n')
-        file.write('              #if(mod(I,3)=0)\n')
-        file.write('                 #write(MeshFile,"\\n    ")\n')
-        file.write('              #end \n')
-        file.write('           #end\n')
-        file.write('        #end \n')
-        file.write('        #switch(Write)\n')
-        file.write('           #case(1)\n')
-        file.write('              #write(MeshFile,"\\n  }\\n")\n')
-        file.write('           #break\n')
-        file.write('           #case(2)\n')
-        file.write('              #write(MeshFile,"\\n")\n')
-        file.write('           #break\n')
-        file.write('           #case(3)\n')
-        file.write('              //do nothing\n')
-        file.write('           #break\n')
-        file.write('           #case(4)\n')
-        file.write('              #write(MeshFile,"\\n}\\n")\n')
-        file.write('           #break\n')
-        file.write('        #end\n')
-        file.write('     }\n')
-        file.write('\n')
-        file.write('     #debug concat("   - face_indices\\n")   \n')
-        file.write('     #declare NumFaces=U*V*2;\n')
-        file.write('     #switch(Write)\n')
-        file.write('        #case(1)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile,\n')
-        file.write('              "  face_indices {\\n"\n')
-        file.write('              "    ", str(NumFaces,0,0),"\\n    "\n')
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('        #case(2)\n')
-        file.write('           #write (\n')
-        file.write('              MeshFile,\n')
-        file.write('              "# faces: ",str(NumFaces,0,0),"\\n"\n')
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('        #case(3)\n')
-        file.write('           #write (\n')
-        file.write('              MeshFile,\n')
-        file.write('              "0,",str(NumFaces,0,0),",\\n"\n')
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('        #case(4)\n')
-        file.write('           #write(\n')
-        file.write('              MeshFile,\n')
-        file.write('              "#declare FaceIndices= array[",str(NumFaces,0,0),"] {\\n  "\n')
-        file.write('           )\n')
-        file.write('        #break\n')
-        file.write('     #end\n')
-        file.write('     face_indices {\n')
-        file.write('        NumFaces\n')
-        file.write('        #local I=0;\n')
-        file.write('        #local H=0;\n')
-        file.write('        #local NumVertices=dimension_size(VecArr,1);\n')
-        file.write('        #while (I<V)\n')
-        file.write('           #local J=0;\n')
-        file.write('           #while (J<U)\n')
-        file.write('              #local Ind=(I*U)+I+J;\n')
-        file.write('              <Ind, Ind+1, Ind+U+2>, <Ind, Ind+U+1, Ind+U+2>\n')
-        file.write('              #switch(Write)\n')
-        file.write('                 #case(1)\n')
-        file.write('                    #write(\n')
-        file.write('                       MeshFile,\n')
-        file.write('                       <Ind, Ind+1, Ind+U+2>, <Ind, Ind+U+1, Ind+U+2>\n')
-        file.write('                    )\n')
-        file.write('                 #break\n')
-        file.write('                 #case(2)\n')
-        file.write('                    #write(\n')
-        file.write('                       MeshFile,\n')
-        file.write(
-            '                       "f ",Ind+1,"/",Ind+1,"/",Ind+1," ",Ind+1+1,"/",Ind+1+1,"/",Ind+1+1," ",Ind+U+2+1,"/",Ind+U+2+1,"/",Ind+U+2+1,"\\n",\n'
-        )
-        file.write(
-            '                       "f ",Ind+U+1+1,"/",Ind+U+1+1,"/",Ind+U+1+1," ",Ind+1,"/",Ind+1,"/",Ind+1," ",Ind+U+2+1,"/",Ind+U+2+1,"/",Ind+U+2+1,"\\n"\n'
-        )
-        file.write('                    )\n')
-        file.write('                 #break\n')
-        file.write('                 #case(3)\n')
-        file.write('                    #write(\n')
-        file.write('                       MeshFile,\n')
-        file.write(
-            '                       Ind,",",Ind+NumVertices,",",Ind+1,",",Ind+1+NumVertices,",",Ind+U+2,",",Ind+U+2+NumVertices,",\\n"\n'
-        )
-        file.write(
-            '                       Ind+U+1,",",Ind+U+1+NumVertices,",",Ind,",",Ind+NumVertices,",",Ind+U+2,",",Ind+U+2+NumVertices,",\\n"\n'
-        )
-        file.write('                    )\n')
-        file.write('                 #break\n')
-        file.write('                 #case(4)\n')
-        file.write('                    #write(\n')
-        file.write('                       MeshFile,\n')
-        file.write('                       <Ind, Ind+1, Ind+U+2>, <Ind, Ind+U+1, Ind+U+2>\n')
-        file.write('                    )\n')
-        file.write('                 #break\n')
-        file.write('              #end\n')
-        file.write('              #local J=J+1;\n')
-        file.write('              #local H=H+1;\n')
-        file.write('              #if(Write=1 | Write=4)\n')
-        file.write('                 #if(mod(H,3)=0)\n')
-        file.write('                    #write(MeshFile,"\\n    ")\n')
-        file.write('                 #end \n')
-        file.write('              #end\n')
-        file.write('           #end\n')
-        file.write('           #local I=I+1;\n')
-        file.write('        #end\n')
-        file.write('     }\n')
-        file.write('     #switch(Write)\n')
-        file.write('        #case(1)\n')
-        file.write('           #write(MeshFile, "\\n  }\\n}")\n')
-        file.write('           #fclose MeshFile\n')
-        file.write('           #debug concat(" Done writing\\n")\n')
-        file.write('        #break\n')
-        file.write('        #case(2)\n')
-        file.write('           #fclose MeshFile\n')
-        file.write('           #debug concat(" Done writing\\n")\n')
-        file.write('        #break\n')
-        file.write('        #case(3)\n')
-        file.write('           #fclose MeshFile\n')
-        file.write('           #debug concat(" Done writing\\n")\n')
-        file.write('        #break\n')
-        file.write('        #case(4)\n')
-        file.write('           #write(MeshFile, "\\n}\\n}")\n')
-        file.write('           #fclose MeshFile\n')
-        file.write('           #debug concat(" Done writing\\n")\n')
-        file.write('        #break\n')
-        file.write('     #end\n')
-        file.write('  }\n')
-        file.write('#end\n')
-
-        file.write('#macro MSM(SplineArray, SplRes, Interp_type,  InterpRes, FileName)\n')
-        file.write('    #declare Build=CheckFileName(FileName);\n')
-        file.write('    #if(Build=0)\n')
-        file.write('        #debug concat("\\n Parsing mesh2 from file: ", FileName, "\\n")\n')
-        file.write('        #include FileName\n')
-        file.write('        object{Surface}\n')
-        file.write('    #else\n')
-        file.write('        #local NumVertices=(SplRes+1)*(InterpRes+1);\n')
-        file.write('        #local NumFaces=SplRes*InterpRes*2;\n')
-        file.write(
-            '        #debug concat("\\n Calculating ",str(NumVertices,0,0)," vertices for ", str(NumFaces,0,0)," triangles\\n\\n")\n'
-        )
-        file.write('        #local VecArr=array[NumVertices]\n')
-        file.write('        #local NormArr=array[NumVertices]\n')
-        file.write('        #local UVArr=array[NumVertices]\n')
-        file.write('        #local N=dimension_size(SplineArray,1);\n')
-        file.write('        #local TempSplArr0=array[N];\n')
-        file.write('        #local TempSplArr1=array[N];\n')
-        file.write('        #local TempSplArr2=array[N];\n')
-        file.write('        #local PosStep=1/SplRes;\n')
-        file.write('        #local InterpStep=1/InterpRes;\n')
-        file.write('        #local Count=0;\n')
-        file.write('        #local Pos=0;\n')
-        file.write('        #while(Pos<=1)\n')
-        file.write('            #local I=0;\n')
-        file.write('            #if (Pos=0)\n')
-        file.write('                #while (I<N)\n')
-        file.write('                    #local Spl=spline{SplineArray[I]}\n')
-        file.write('                    #local TempSplArr0[I]=<0,0,0>+Spl(Pos);\n')
-        file.write('                    #local TempSplArr1[I]=<0,0,0>+Spl(Pos+PosStep);\n')
-        file.write('                    #local TempSplArr2[I]=<0,0,0>+Spl(Pos-PosStep);\n')
-        file.write('                    #local I=I+1;\n')
-        file.write('                #end\n')
-        file.write('                #local S0=BuildSpline(TempSplArr0, Interp_type)\n')
-        file.write('                #local S1=BuildSpline(TempSplArr1, Interp_type)\n')
-        file.write('                #local S2=BuildSpline(TempSplArr2, Interp_type)\n')
-        file.write('            #else\n')
-        file.write('                #while (I<N)\n')
-        file.write('                    #local Spl=spline{SplineArray[I]}\n')
-        file.write('                    #local TempSplArr1[I]=<0,0,0>+Spl(Pos+PosStep);\n')
-        file.write('                    #local I=I+1;\n')
-        file.write('                #end\n')
-        file.write('                #local S1=BuildSpline(TempSplArr1, Interp_type)\n')
-        file.write('            #end\n')
-        file.write('            #local J=0;\n')
-        file.write('            #while (J<=1)\n')
-        file.write('                #local P0=<0,0,0>+S0(J);\n')
-        file.write('                #local P1=<0,0,0>+S1(J);\n')
-        file.write('                #local P2=<0,0,0>+S2(J);\n')
-        file.write('                #local P3=<0,0,0>+S0(J+InterpStep);\n')
-        file.write('                #local P4=<0,0,0>+S0(J-InterpStep);\n')
-        file.write('                #local B1=P4-P0;\n')
-        file.write('                #local B2=P2-P0;\n')
-        file.write('                #local B3=P3-P0;\n')
-        file.write('                #local B4=P1-P0;\n')
-        file.write('                #local N1=vcross(B1,B2);\n')
-        file.write('                #local N2=vcross(B2,B3);\n')
-        file.write('                #local N3=vcross(B3,B4);\n')
-        file.write('                #local N4=vcross(B4,B1);\n')
-        file.write('                #local Norm=vnormalize((N1+N2+N3+N4));\n')
-        file.write('                #local VecArr[Count]=P0;\n')
-        file.write('                #local NormArr[Count]=Norm;\n')
-        file.write('                #local UVArr[Count]=<J,Pos>;\n')
-        file.write('                #local J=J+InterpStep;\n')
-        file.write('                #local Count=Count+1;\n')
-        file.write('            #end\n')
-        file.write('            #local S2=spline{S0}\n')
-        file.write('            #local S0=spline{S1}\n')
-        file.write(
-            '            #debug concat("\\r Done ", str(Count,0,0)," vertices : ", str(100*Count/NumVertices,0,2)," %")\n'
-        )
-        file.write('            #local Pos=Pos+PosStep;\n')
-        file.write('        #end\n')
-        file.write('        BuildWriteMesh2(VecArr, NormArr, UVArr, InterpRes, SplRes, "")\n')
-        file.write('    #end\n')
-        file.write('#end\n\n')
-
-        file.write('#macro Coons(Spl1, Spl2, Spl3, Spl4, Iter_U, Iter_V, FileName)\n')
-        file.write('   #declare Build=CheckFileName(FileName);\n')
-        file.write('   #if(Build=0)\n')
-        file.write('      #debug concat("\\n Parsing mesh2 from file: ", FileName, "\\n")\n')
-        file.write('      #include FileName\n')
-        file.write('      object{Surface}\n')
-        file.write('   #else\n')
-        file.write('      #local NumVertices=(Iter_U+1)*(Iter_V+1);\n')
-        file.write('      #local NumFaces=Iter_U*Iter_V*2;\n')
-        file.write(
-            '      #debug concat("\\n Calculating ", str(NumVertices,0,0), " vertices for ",str(NumFaces,0,0), " triangles\\n\\n")\n'
-        )
-        file.write('      #declare VecArr=array[NumVertices]   \n')
-        file.write('      #declare NormArr=array[NumVertices]   \n')
-        file.write('      #local UVArr=array[NumVertices]      \n')
-        file.write('      #local Spl1_0=Spl1(0);\n')
-        file.write('      #local Spl2_0=Spl2(0);\n')
-        file.write('      #local Spl3_0=Spl3(0);\n')
-        file.write('      #local Spl4_0=Spl4(0);\n')
-        file.write('      #local UStep=1/Iter_U;\n')
-        file.write('      #local VStep=1/Iter_V;\n')
-        file.write('      #local Count=0;\n')
-        file.write('      #local I=0;\n')
-        file.write('      #while (I<=1)\n')
-        file.write('         #local Im=1-I;\n')
-        file.write('         #local J=0;\n')
-        file.write('         #while (J<=1)\n')
-        file.write('            #local Jm=1-J;\n')
-        file.write(
-            '            #local C0=Im*Jm*(Spl1_0)+Im*J*(Spl2_0)+I*J*(Spl3_0)+I*Jm*(Spl4_0);\n'
-        )
-        file.write('            #local P0=LInterpolate(I, Spl1(J), Spl3(Jm)) + \n')
-        file.write('               LInterpolate(Jm, Spl2(I), Spl4(Im))-C0;\n')
-        file.write('            #declare VecArr[Count]=P0;\n')
-        file.write('            #local UVArr[Count]=<J,I>;\n')
-        file.write('            #local J=J+UStep;\n')
-        file.write('            #local Count=Count+1;\n')
-        file.write('         #end\n')
-        file.write('         #debug concat(\n')
-        file.write('            "\r Done ", str(Count,0,0)," vertices :         ",\n')
-        file.write('            str(100*Count/NumVertices,0,2)," %"\n')
-        file.write('         )\n')
-        file.write('         #local I=I+VStep;\n')
-        file.write('      #end\n')
-        file.write('      #debug "\r Normals                                  "\n')
-        file.write('      #local Count=0;\n')
-        file.write('      #local I=0;\n')
-        file.write('      #while (I<=Iter_V)\n')
-        file.write('         #local J=0;\n')
-        file.write('         #while (J<=Iter_U)\n')
-        file.write('            #local Ind=(I*Iter_U)+I+J;\n')
-        file.write('            #local P0=VecArr[Ind];\n')
-        file.write('            #if(J=0)\n')
-        file.write('               #local P1=P0+(P0-VecArr[Ind+1]);\n')
-        file.write('            #else\n')
-        file.write('               #local P1=VecArr[Ind-1];\n')
-        file.write('            #end\n')
-        file.write('            #if (J=Iter_U)\n')
-        file.write('               #local P2=P0+(P0-VecArr[Ind-1]);\n')
-        file.write('            #else\n')
-        file.write('               #local P2=VecArr[Ind+1];\n')
-        file.write('            #end\n')
-        file.write('            #if (I=0)\n')
-        file.write('               #local P3=P0+(P0-VecArr[Ind+Iter_U+1]);\n')
-        file.write('            #else\n')
-        file.write('               #local P3=VecArr[Ind-Iter_U-1];\n')
-        file.write('            #end\n')
-        file.write('            #if (I=Iter_V)\n')
-        file.write('               #local P4=P0+(P0-VecArr[Ind-Iter_U-1]);\n')
-        file.write('            #else\n')
-        file.write('               #local P4=VecArr[Ind+Iter_U+1];\n')
-        file.write('            #end\n')
-        file.write('            #local B1=P4-P0;\n')
-        file.write('            #local B2=P2-P0;\n')
-        file.write('            #local B3=P3-P0;\n')
-        file.write('            #local B4=P1-P0;\n')
-        file.write('            #local N1=vcross(B1,B2);\n')
-        file.write('            #local N2=vcross(B2,B3);\n')
-        file.write('            #local N3=vcross(B3,B4);\n')
-        file.write('            #local N4=vcross(B4,B1);\n')
-        file.write('            #local Norm=vnormalize((N1+N2+N3+N4));\n')
-        file.write('            #declare NormArr[Count]=Norm;\n')
-        file.write('            #local J=J+1;\n')
-        file.write('            #local Count=Count+1;\n')
-        file.write('         #end\n')
-        file.write(
-            '         #debug concat("\r Done ", str(Count,0,0)," normals : ",str(100*Count/NumVertices,0,2), " %")\n'
-        )
-        file.write('         #local I=I+1;\n')
-        file.write('      #end\n')
-        file.write('      BuildWriteMesh2(VecArr, NormArr, UVArr, Iter_U, Iter_V, FileName)\n')
-        file.write('   #end\n')
-        file.write('#end\n\n')
-    # Empty curves
-    if len(ob.data.splines) == 0:
-        tab_write("\n//dummy sphere to represent empty curve location\n")
-        tab_write("#declare %s =\n" % dataname)
-        tab_write(
-            "sphere {<%.6g, %.6g, %.6g>,0 pigment{rgbt 1} "
-            "no_image no_reflection no_radiosity "
-            "photons{pass_through collect off} hollow}\n\n"
-            % (ob.location.x, ob.location.y, ob.location.z)
-        )  # ob.name > povdataname)
-    # And non empty curves
-    else:
-        if not bezier_sweep:
-            tab_write("#declare %s =\n" % dataname)
-        if ob.pov.curveshape == 'sphere_sweep' and not bezier_sweep:
-            tab_write("union {\n")
-            for spl in ob.data.splines:
-                if spl.type != "BEZIER":
-                    spl_type = "linear"
-                    if spl.type == "NURBS":
-                        spl_type = "cubic"
-                    points = spl.points
-                    num_points = len(points)
-                    if spl.use_cyclic_u:
-                        num_points += 3
-
-                    tab_write("sphere_sweep { %s_spline %s,\n" % (spl_type, num_points))
-                    if spl.use_cyclic_u:
-                        pt1 = points[len(points) - 1]
-                        wpt1 = pt1.co
-                        tab_write(
-                            "<%.4g,%.4g,%.4g>,%.4g\n"
-                            % (wpt1[0], wpt1[1], wpt1[2], pt1.radius * ob.data.bevel_depth)
-                        )
-                    for pt in points:
-                        wpt = pt.co
-                        tab_write(
-                            "<%.4g,%.4g,%.4g>,%.4g\n"
-                            % (wpt[0], wpt[1], wpt[2], pt.radius * ob.data.bevel_depth)
-                        )
-                    if spl.use_cyclic_u:
-                        for i in range(0, 2):
-                            end_pt = points[i]
-                            wpt = end_pt.co
-                            tab_write(
-                                "<%.4g,%.4g,%.4g>,%.4g\n"
-                                % (wpt[0], wpt[1], wpt[2], end_pt.radius * ob.data.bevel_depth)
-                            )
-
-                tab_write("}\n")
-        # below not used yet?
-        if ob.pov.curveshape == 'sor':
-            for spl in ob.data.splines:
-                if spl.type in {'POLY', 'NURBS'}:
-                    points = spl.points
-                    num_points = len(points)
-                    tab_write("sor { %s,\n" % num_points)
-                    for pt in points:
-                        wpt = pt.co
-                        tab_write("<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
-                else:
-                    tab_write("box { 0,0\n")
-        if ob.pov.curveshape in {'lathe', 'prism'}:
-            spl = ob.data.splines[0]
-            if spl.type == "BEZIER":
-                points = spl.bezier_points
-                len_cur = len(points) - 1
-                len_pts = len_cur * 4
-                ifprism = ''
-                if ob.pov.curveshape in {'prism'}:
-                    height = ob.data.extrude
-                    ifprism = '-%s, %s,' % (height, height)
-                    len_cur += 1
-                    len_pts += 4
-                tab_write("%s { bezier_spline %s %s,\n" % (ob.pov.curveshape, ifprism, len_pts))
-                for i in range(0, len_cur):
-                    p1 = points[i].co
-                    pR = points[i].handle_right
-                    end = i + 1
-                    if i == len_cur - 1 and ob.pov.curveshape in {'prism'}:
-                        end = 0
-                    pL = points[end].handle_left
-                    p2 = points[end].co
-                    line = "<%.4g,%.4g>" % (p1[0], p1[1])
-                    line += "<%.4g,%.4g>" % (pR[0], pR[1])
-                    line += "<%.4g,%.4g>" % (pL[0], pL[1])
-                    line += "<%.4g,%.4g>" % (p2[0], p2[1])
-                    tab_write("%s\n" % line)
-            else:
-                points = spl.points
-                len_cur = len(points)
-                len_pts = len_cur
-                ifprism = ''
-                if ob.pov.curveshape in {'prism'}:
-                    height = ob.data.extrude
-                    ifprism = '-%s, %s,' % (height, height)
-                    len_pts += 3
-                spl_type = 'quadratic'
-                if spl.type == 'POLY':
-                    spl_type = 'linear'
-                tab_write(
-                    "%s { %s_spline %s %s,\n" % (ob.pov.curveshape, spl_type, ifprism, len_pts)
-                )
-                if ob.pov.curveshape in {'prism'}:
-                    pt = points[len(points) - 1]
-                    wpt = pt.co
-                    tab_write("<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
-                for pt in points:
-                    wpt = pt.co
-                    tab_write("<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
-                if ob.pov.curveshape in {'prism'}:
-                    for i in range(2):
-                        pt = points[i]
-                        wpt = pt.co
-                        tab_write("<%.4g,%.4g>\n" % (wpt[0], wpt[1]))
-        if bezier_sweep:
-            for p in range(len(ob.data.splines)):
-                br = []
-                depth = ob.data.bevel_depth
-                spl = ob.data.splines[p]
-                points = spl.bezier_points
-                len_cur = len(points) - 1
-                num_points = len_cur * 4
-                if spl.use_cyclic_u:
-                    len_cur += 1
-                    num_points += 4
-                tab_write("#declare %s_points_%s = array[%s]{\n" % (dataname, p, num_points))
-                for i in range(len_cur):
-                    p1 = points[i].co
-                    pR = points[i].handle_right
-                    end = i + 1
-                    if spl.use_cyclic_u and i == (len_cur - 1):
-                        end = 0
-                    pL = points[end].handle_left
-                    p2 = points[end].co
-                    r3 = points[end].radius * depth
-                    r0 = points[i].radius * depth
-                    r1 = 2 / 3 * r0 + 1 / 3 * r3
-                    r2 = 1 / 3 * r0 + 2 / 3 * r3
-                    br.append((r0, r1, r2, r3))
-                    line = "<%.4g,%.4g,%.4f>" % (p1[0], p1[1], p1[2])
-                    line += "<%.4g,%.4g,%.4f>" % (pR[0], pR[1], pR[2])
-                    line += "<%.4g,%.4g,%.4f>" % (pL[0], pL[1], pL[2])
-                    line += "<%.4g,%.4g,%.4f>" % (p2[0], p2[1], p2[2])
-                    tab_write("%s\n" % line)
-                tab_write("}\n")
-                tab_write("#declare %s_radii_%s = array[%s]{\n" % (dataname, p, len(br) * 4))
-                for rad_tuple in br:
-                    tab_write(
-                        '%.4f,%.4f,%.4f,%.4f\n'
-                        % (rad_tuple[0], rad_tuple[1], rad_tuple[2], rad_tuple[3])
-                    )
-                tab_write("}\n")
-            if len(ob.data.splines) == 1:
-                p = 1
-                tab_write('#declare %s = object{\n' % dataname)
-                tab_write(
-                    '    Shape_Bezierpoints_Sphere_Sweep(yes,%s, %s_points_%s, %s_radii_%s) \n'
-                    % (ob.data.resolution_u, dataname, p, dataname, p)
-                )
-            else:
-                tab_write('#declare %s = union{\n' % dataname)
-                for p in range(len(ob.data.splines)):
-                    tab_write(
-                        '    object{Shape_Bezierpoints_Sphere_Sweep(yes,%s, %s_points_%s, %s_radii_%s)} \n'
-                        % (ob.data.resolution_u, dataname, p, dataname, p)
-                    )
-                # tab_write('#include "bezier_spheresweep.inc"\n') #now inlined
-            # tab_write('#declare %s = object{Shape_Bezierpoints_Sphere_Sweep(yes,%s, %s_bezier_points, %.4f) \n'%(dataname,ob.data.resolution_u,dataname,ob.data.bevel_depth))
-        if ob.pov.curveshape in {'loft'}:
-            tab_write('object {MSM(%s,%s,"c",%s,"")\n' % (dataname, ob.pov.res_u, ob.pov.res_v))
-        if ob.pov.curveshape in {'birail'}:
-            splines = '%s1,%s2,%s3,%s4' % (dataname, dataname, dataname, dataname)
-            tab_write('object {Coons(%s, %s, %s, "")\n' % (splines, ob.pov.res_u, ob.pov.res_v))
-        # pov_mat_name = "Default_texture" # XXX! Unused, check instantiation
-        if ob.active_material:
-            # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(ob.active_material.name))
-            try:
-                material = ob.active_material
-                write_object_material_interior(material, ob, tab_write)
-            except IndexError:
-                print(ob.data)
-        # tab_write("texture {%s}\n"%pov_mat_name)
-        if ob.pov.curveshape in {'prism'}:
-            tab_write("rotate <90,0,0>\n")
-            tab_write("scale y*-1\n")
-        tab_write("}\n")
diff --git a/render_povray/object_mesh_topology.py b/render_povray/object_mesh_topology.py
deleted file mode 100755
index cad028a1b..000000000
--- a/render_povray/object_mesh_topology.py
+++ /dev/null
@@ -1,1534 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-# <pep8 compliant>
-
-"""Translate to POV the control point compounded geometries like 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 .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],
-           )
-
-
-def write_object_csg_inside_vector(ob, file):
-    """Write inside vector for use by pov CSG, only once per object using boolean"""
-    has_csg_inside_vector = False
-    for modif in ob.modifiers:
-        if (
-                not has_csg_inside_vector
-                and modif.type == 'BOOLEAN'
-                and ob.pov.boolean_mod == "POV"
-        ):
-            file.write(
-                "\tinside_vector <%.6g, %.6g, %.6g>\n"
-                % (
-                    ob.pov.inside_vector[0],
-                    ob.pov.inside_vector[1],
-                    ob.pov.inside_vector[2],
-                )
-            )
-            has_csg_inside_vector = True
-
-
-#    objectNames = {}
-DEF_OBJ_NAME = "Default"
-
-
-def export_meshes(
-        preview_dir,
-        file,
-        scene,
-        sel,
-        csg,
-        string_strip_hyphen,
-        safety,
-        write_object_modifiers,
-        material_names_dictionary,
-        write_object_material_interior,
-        exported_lights_count,
-        unpacked_images,
-        image_format,
-        img_map,
-        img_map_transforms,
-        path_image,
-        smoke_path,
-        global_matrix,
-        write_matrix,
-        using_uberpov,
-        comments,
-        linebreaksinlists,
-        tab,
-        tab_level,
-        tab_write,
-        info_callback,
-):
-    """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.
-    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 not (ob.is_instancer and ob.original != ob):
-            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, write_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 not fluid_found:
-                # No fluid found
-                if hasattr(ob, 'particle_systems'):
-                    # Importing function Export Hair
-                    # here rather than at the top recommended for addons startup footprint
-                    from .object_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, write_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("\n//dummy sphere to represent Empty location\n")
-                        tab_write(
-                            "#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.
-
-                    ob_eval = ob  # not sure this is needed in case to_mesh_clear could damage obj ?
-                    try:
-                        me = ob_eval.to_mesh()
-
-                    # Here identify the exception for mesh object with no data: Runtime-Error ?
-                    # So we can write something for the dataname or maybe treated "if not me" below
-                    except BaseException as e:
-                        print(e.__doc__)
-                        print('An exception occurred: {}'.format(e))
-                        # also happens when curves cant be made into meshes because of no-data
-                        continue
-
-                    importance = ob.pov.importance_value
-                    if me:
-                        me.calc_loop_triangles()
-                        me_materials = me.materials
-                        me_faces = me.loop_triangles[:]
-                        # --- numpytest
-                        # me_looptris = me.loops
-
-                        # Below otypes = ['int32'] is a 32-bit signed integer number numpy datatype
-                        # get_v_index = np.vectorize(lambda l: l.vertex_index, otypes = ['int32'], cache = True)
-                        # faces_verts_idx = get_v_index(me_looptris)
-
-                    # if len(me_faces)==0:
-                    # tab_write("\n//dummy sphere to represent empty mesh location\n")
-                    # tab_write("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname)
-
-                    if not me or not me_faces:
-                        tab_write("\n//dummy sphere to represent empty mesh location\n")
-                        tab_write(
-                            "#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
-
-                    uv_layers = me.uv_layers
-                    if len(uv_layers) > 0:
-                        if me.uv_layers.active and uv_layers.active.data:
-                            uv_layer = uv_layers.active.data
-                    else:
-                        uv_layer = None
-
-                    try:
-                        # vcol_layer = me.vertex_colors.active.data
-                        vcol_layer = me.vertex_colors.active.data
-                    except AttributeError:
-                        vcol_layer = None
-
-                    faces_verts = [f.vertices[:] for f in me_faces]
-                    faces_normals = [f.normal[:] for f in me_faces]
-                    verts_normals = [v.normal[:] for v in me.vertices]
-
-                    # Use named declaration to allow reference e.g. for baking. MR
-                    file.write("\n")
-                    tab_write("#declare %s =\n" % povdataname)
-                    tab_write("mesh2 {\n")
-                    tab_write("vertex_vectors {\n")
-                    tab_write("%d" % len(me.vertices))  # vert count
-
-                    tab_str = tab * tab_level
-                    for v in me.vertices:
-                        if linebreaksinlists:
-                            file.write(",\n")
-                            file.write(tab_str + "<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
-                        else:
-                            file.write(", ")
-                            file.write("<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
-                        # tab_write("<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
-                    file.write("\n")
-                    tab_write("}\n")
-
-                    # Build unique Normal list
-                    uniqueNormals = {}
-                    for fi, f in enumerate(me_faces):
-                        fv = faces_verts[fi]
-                        # [-1] is a dummy index, use a list so we can modify in place
-                        if f.use_smooth:  # Use vertex normals
-                            for v in fv:
-                                key = verts_normals[v]
-                                uniqueNormals[key] = [-1]
-                        else:  # Use face normal
-                            key = faces_normals[fi]
-                            uniqueNormals[key] = [-1]
-
-                    tab_write("normal_vectors {\n")
-                    tab_write("%d" % len(uniqueNormals))  # vert count
-                    idx = 0
-                    tab_str = tab * tab_level
-                    for no, index in uniqueNormals.items():
-                        if linebreaksinlists:
-                            file.write(",\n")
-                            file.write(tab_str + "<%.6f, %.6f, %.6f>" % no)  # vert count
-                        else:
-                            file.write(", ")
-                            file.write("<%.6f, %.6f, %.6f>" % no)  # vert count
-                        index[0] = idx
-                        idx += 1
-                    file.write("\n")
-                    tab_write("}\n")
-
-                    # Vertex colors
-                    vertCols = {}  # Use for material colors also.
-
-                    if uv_layer:
-                        # Generate unique UV's
-                        uniqueUVs = {}
-                        # n = 0
-                        for f in me_faces:  # me.faces in 2.7
-                            uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]
-
-                            for uv in uvs:
-                                uniqueUVs[uv[:]] = [-1]
-
-                        tab_write("uv_vectors {\n")
-                        # print unique_uvs
-                        tab_write("%d" % len(uniqueUVs))  # vert count
-                        idx = 0
-                        tab_str = tab * tab_level
-                        for uv, index in uniqueUVs.items():
-                            if linebreaksinlists:
-                                file.write(",\n")
-                                file.write(tab_str + "<%.6f, %.6f>" % uv)
-                            else:
-                                file.write(", ")
-                                file.write("<%.6f, %.6f>" % uv)
-                            index[0] = idx
-                            idx += 1
-                        '''
-                        else:
-                            # Just add 1 dummy vector, no real UV's
-                            tab_write('1') # vert count
-                            file.write(',\n\t\t<0.0, 0.0>')
-                        '''
-                        file.write("\n")
-                        tab_write("}\n")
-
-                    if me.vertex_colors:
-                        # Write down vertex colors as a texture for each vertex
-                        tab_write("texture_list {\n")
-                        tab_write("%d\n" % (len(me_faces) * 3))  # assumes we have only triangles
-                        VcolIdx = 0
-                        if comments:
-                            file.write(
-                                "\n  //Vertex colors: one simple pigment texture per vertex\n"
-                            )
-                        for fi, f in enumerate(me_faces):
-                            # annoying, index may be invalid
-                            material_index = f.material_index
-                            try:
-                                material = me_materials[material_index]
-                            except BaseException as e:
-                                print(e.__doc__)
-                                print('An exception occurred: {}'.format(e))
-                                material = None
-                            if (
-                                    material
-                            ):  # and material.use_vertex_color_paint: #Always use vertex color when there is some for now
-
-                                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]
-
-                                for col in cols:
-                                    key = (
-                                        col[0],
-                                        col[1],
-                                        col[2],
-                                        material_index,
-                                    )  # Material index!
-                                    VcolIdx += 1
-                                    vertCols[key] = [VcolIdx]
-                                    if linebreaksinlists:
-                                        tab_write(
-                                            "texture {pigment{ color srgb <%6f,%6f,%6f> }}\n"
-                                            % (col[0], col[1], col[2])
-                                        )
-                                    else:
-                                        tab_write(
-                                            "texture {pigment{ color srgb <%6f,%6f,%6f> }}"
-                                            % (col[0], col[1], col[2])
-                                        )
-                                        tab_str = tab * tab_level
-                            else:
-                                if material:
-                                    # Multiply diffuse with SSS Color
-                                    if material.pov_subsurface_scattering.use:
-                                        diffuse_color = [
-                                            i * j
-                                            for i, j in zip(
-                                                material.pov_subsurface_scattering.color[:],
-                                                material.diffuse_color[:],
-                                            )
-                                        ]
-                                        key = (
-                                            diffuse_color[0],
-                                            diffuse_color[1],
-                                            diffuse_color[2],
-                                            material_index,
-                                        )
-                                        vertCols[key] = [-1]
-                                    else:
-                                        diffuse_color = material.diffuse_color[:]
-                                        key = (
-                                            diffuse_color[0],
-                                            diffuse_color[1],
-                                            diffuse_color[2],
-                                            material_index,
-                                        )
-                                        vertCols[key] = [-1]
-
-                        tab_write("\n}\n")
-                        # Face indices
-                        tab_write("\nface_indices {\n")
-                        tab_write("%d" % (len(me_faces)))  # faces count
-                        tab_str = tab * tab_level
-
-                        for fi, f in enumerate(me_faces):
-                            fv = faces_verts[fi]
-                            material_index = f.material_index
-
-                            if vcol_layer:
-                                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]
-
-                            if (
-                                    not me_materials or me_materials[material_index] is None
-                            ):  # No materials
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    # vert count
-                                    file.write(tab_str + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
-                                else:
-                                    file.write(", ")
-                                    file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2]))  # vert count
-                            else:
-                                material = me_materials[material_index]
-                                if me.vertex_colors:  # and material.use_vertex_color_paint:
-                                    # Color per vertex - vertex color
-
-                                    col1 = cols[0]
-                                    col2 = cols[1]
-                                    col3 = cols[2]
-
-                                    ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
-                                    ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
-                                    ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
-                                else:
-                                    # Color per material - flat material color
-                                    if material.pov_subsurface_scattering.use:
-                                        diffuse_color = [
-                                            i * j
-                                            for i, j in zip(
-                                                material.pov_subsurface_scattering.color[:],
-                                                material.diffuse_color[:],
-                                            )
-                                        ]
-                                    else:
-                                        diffuse_color = material.diffuse_color[:]
-                                    ci1 = ci2 = ci3 = vertCols[
-                                        diffuse_color[0],
-                                        diffuse_color[1],
-                                        diffuse_color[2],
-                                        f.material_index,
-                                    ][0]
-                                    # ci are zero based index so we'll subtract 1 from them
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str
-                                        + "<%d,%d,%d>, %d,%d,%d"
-                                        % (fv[0], fv[1], fv[2], ci1 - 1, ci2 - 1, ci3 - 1)
-                                    )  # vert count
-                                else:
-                                    file.write(", ")
-                                    file.write(
-                                        "<%d,%d,%d>, %d,%d,%d"
-                                        % (fv[0], fv[1], fv[2], ci1 - 1, ci2 - 1, ci3 - 1)
-                                    )  # vert count
-
-                        file.write("\n")
-                        tab_write("}\n")
-
-                        # normal_indices indices
-                        tab_write("normal_indices {\n")
-                        tab_write("%d" % (len(me_faces)))  # faces count
-                        tab_str = tab * tab_level
-                        for fi, fv in enumerate(faces_verts):
-
-                            if me_faces[fi].use_smooth:
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str
-                                        + "<%d,%d,%d>"
-                                        % (
-                                            uniqueNormals[verts_normals[fv[0]]][0],
-                                            uniqueNormals[verts_normals[fv[1]]][0],
-                                            uniqueNormals[verts_normals[fv[2]]][0],
-                                        )
-                                    )  # vert count
-                                else:
-                                    file.write(", ")
-                                    file.write(
-                                        "<%d,%d,%d>"
-                                        % (
-                                            uniqueNormals[verts_normals[fv[0]]][0],
-                                            uniqueNormals[verts_normals[fv[1]]][0],
-                                            uniqueNormals[verts_normals[fv[2]]][0],
-                                        )
-                                    )  # vert count
-                            else:
-                                idx = uniqueNormals[faces_normals[fi]][0]
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str + "<%d,%d,%d>" % (idx, idx, idx)
-                                    )  # vert count
-                                else:
-                                    file.write(", ")
-                                    file.write("<%d,%d,%d>" % (idx, idx, idx))  # vert count
-
-                        file.write("\n")
-                        tab_write("}\n")
-
-                        if uv_layer:
-                            tab_write("uv_indices {\n")
-                            tab_write("%d" % (len(me_faces)))  # faces count
-                            tab_str = tab * tab_level
-                            for f in me_faces:
-                                uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]
-
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str
-                                        + "<%d,%d,%d>"
-                                        % (
-                                            uniqueUVs[uvs[0]][0],
-                                            uniqueUVs[uvs[1]][0],
-                                            uniqueUVs[uvs[2]][0],
-                                        )
-                                    )
-                                else:
-                                    file.write(", ")
-                                    file.write(
-                                        "<%d,%d,%d>"
-                                        % (
-                                            uniqueUVs[uvs[0]][0],
-                                            uniqueUVs[uvs[1]][0],
-                                            uniqueUVs[uvs[2]][0],
-                                        )
-                                    )
-
-                            file.write("\n")
-                            tab_write("}\n")
-
-                        # XXX BOOLEAN
-                        write_object_csg_inside_vector(ob, file)
-
-                        if me.materials:
-                            try:
-                                material = me.materials[0]  # dodgy
-                                write_object_material_interior(material, ob, tab_write)
-                            except IndexError:
-                                print(me)
-
-                        # POV object modifiers such as
-                        # hollow / sturm / double_illuminate etc.
-                        write_object_modifiers(ob, file)
-
-                        # Importance for radiosity sampling added here:
-                        tab_write("radiosity { \n")
-                        tab_write("importance %3g \n" % importance)
-                        tab_write("}\n")
-
-                        tab_write("}\n")  # End of mesh block
-                    else:
-                        facesMaterials = []  # WARNING!!!!!!!!!!!!!!!!!!!!!!
-                        if me_materials:
-                            for f in me_faces:
-                                if f.material_index not in facesMaterials:
-                                    facesMaterials.append(f.material_index)
-                        # No vertex colors, so write material colors as vertex colors
-                        for i, material in enumerate(me_materials):
-
-                            if (
-                                    material and material.pov.material_use_nodes is False
-                            ):  # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                                # Multiply diffuse with SSS Color
-                                if material.pov_subsurface_scattering.use:
-                                    diffuse_color = [
-                                        i * j
-                                        for i, j in zip(
-                                            material.pov_subsurface_scattering.color[:],
-                                            material.diffuse_color[:],
-                                        )
-                                    ]
-                                    key = (
-                                        diffuse_color[0],
-                                        diffuse_color[1],
-                                        diffuse_color[2],
-                                        i,
-                                    )  # i == f.mat
-                                    vertCols[key] = [-1]
-                                else:
-                                    diffuse_color = material.diffuse_color[:]
-                                    key = (
-                                        diffuse_color[0],
-                                        diffuse_color[1],
-                                        diffuse_color[2],
-                                        i,
-                                    )  # i == f.mat
-                                    vertCols[key] = [-1]
-
-                                idx = 0
-                                local_material_names = []  # XXX track and revert
-                                material_finish = None
-                                for col, index in vertCols.items():
-                                    # if me_materials:
-                                    mater = me_materials[col[3]]
-                                    if me_materials is not None:
-                                        texturing.write_texture_influence(
-                                            using_uberpov,
-                                            mater,
-                                            material_names_dictionary,
-                                            local_material_names,
-                                            path_image,
-                                            exported_lights_count,
-                                            image_format,
-                                            img_map,
-                                            img_map_transforms,
-                                            tab_write,
-                                            comments,
-                                            string_strip_hyphen,
-                                            safety,
-                                            col,
-                                            preview_dir,
-                                            unpacked_images,
-                                        )
-                                    # ------------------------------------------------
-                                    index[0] = idx
-                                    idx += 1
-
-                        # Vert Colors
-                        tab_write("texture_list {\n")
-                        # In case there's is no material slot, give at least one texture
-                        # (an empty one so it uses pov default)
-                        if len(vertCols) == 0:
-                            file.write(tab_str + "1")
-                        else:
-                            file.write(tab_str + "%s" % (len(vertCols)))  # vert count
-
-                        # below "material" alias, added check obj.active_material
-                        # to avoid variable referenced before assignment error
-                        try:
-                            material = ob.active_material
-                        except IndexError:
-                            # when no material slot exists,
-                            material = None
-
-                        # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                        if (
-                                material
-                                and ob.active_material is not None
-                                and not material.pov.material_use_nodes
-                        ):
-                            if material.pov.replacement_text != "":
-                                file.write("\n")
-                                file.write(" texture{%s}\n" % material.pov.replacement_text)
-
-                            else:
-                                # Loop through declared materials list
-                                for cMN in local_material_names:
-                                    if material != "Default":
-                                        file.write("\n texture{MAT_%s}\n" % cMN)
-                                        # use string_strip_hyphen(material_names_dictionary[material]))
-                                        # or Something like that to clean up the above?
-                        elif material and material.pov.material_use_nodes:
-                            for index in facesMaterials:
-                                faceMaterial = string_strip_hyphen(
-                                    bpy.path.clean_name(me_materials[index].name)
-                                )
-                                file.write("\n texture{%s}\n" % faceMaterial)
-                        # END!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                        else:
-                            file.write(" texture{}\n")
-                        tab_write("}\n")
-
-                        # Face indices
-                        tab_write("face_indices {\n")
-                        tab_write("%d" % (len(me_faces)))  # faces count
-                        tab_str = tab * tab_level
-
-                        for fi, f in enumerate(me_faces):
-                            fv = faces_verts[fi]
-                            material_index = f.material_index
-
-                            if vcol_layer:
-                                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]
-
-                            if (
-                                    not me_materials or me_materials[material_index] is None
-                            ):  # No materials
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    # vert count
-                                    file.write(tab_str + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
-                                else:
-                                    file.write(", ")
-                                    file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2]))  # vert count
-                            else:
-                                material = me_materials[material_index]
-                                ci1 = ci2 = ci3 = f.material_index
-                                if me.vertex_colors:  # and material.use_vertex_color_paint:
-                                    # Color per vertex - vertex color
-
-                                    col1 = cols[0]
-                                    col2 = cols[1]
-                                    col3 = cols[2]
-
-                                    ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
-                                    ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
-                                    ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
-                                elif material.pov.material_use_nodes:
-                                    ci1 = ci2 = ci3 = 0
-                                else:
-                                    # Color per material - flat material color
-                                    if material.pov_subsurface_scattering.use:
-                                        diffuse_color = [
-                                            i * j
-                                            for i, j in zip(
-                                                material.pov_subsurface_scattering.color[:],
-                                                material.diffuse_color[:],
-                                            )
-                                        ]
-                                    else:
-                                        diffuse_color = material.diffuse_color[:]
-                                    ci1 = ci2 = ci3 = vertCols[
-                                        diffuse_color[0],
-                                        diffuse_color[1],
-                                        diffuse_color[2],
-                                        f.material_index,
-                                    ][0]
-
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str
-                                        + "<%d,%d,%d>, %d,%d,%d"
-                                        % (fv[0], fv[1], fv[2], ci1, ci2, ci3)
-                                    )  # vert count
-                                else:
-                                    file.write(", ")
-                                    file.write(
-                                        "<%d,%d,%d>, %d,%d,%d"
-                                        % (fv[0], fv[1], fv[2], ci1, ci2, ci3)
-                                    )  # vert count
-
-                        file.write("\n")
-                        tab_write("}\n")
-
-                        # normal_indices indices
-                        tab_write("normal_indices {\n")
-                        tab_write("%d" % (len(me_faces)))  # faces count
-                        tab_str = tab * tab_level
-                        for fi, fv in enumerate(faces_verts):
-                            if me_faces[fi].use_smooth:
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str
-                                        + "<%d,%d,%d>"
-                                        % (
-                                            uniqueNormals[verts_normals[fv[0]]][0],
-                                            uniqueNormals[verts_normals[fv[1]]][0],
-                                            uniqueNormals[verts_normals[fv[2]]][0],
-                                        )
-                                    )  # vert count
-                                else:
-                                    file.write(", ")
-                                    file.write(
-                                        "<%d,%d,%d>"
-                                        % (
-                                            uniqueNormals[verts_normals[fv[0]]][0],
-                                            uniqueNormals[verts_normals[fv[1]]][0],
-                                            uniqueNormals[verts_normals[fv[2]]][0],
-                                        )
-                                    )  # vert count
-                            else:
-                                idx = uniqueNormals[faces_normals[fi]][0]
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str + "<%d,%d,%d>" % (idx, idx, idx)
-                                    )  # vertcount
-                                else:
-                                    file.write(", ")
-                                    file.write("<%d,%d,%d>" % (idx, idx, idx))  # vert count
-
-                        file.write("\n")
-                        tab_write("}\n")
-
-                        if uv_layer:
-                            tab_write("uv_indices {\n")
-                            tab_write("%d" % (len(me_faces)))  # faces count
-                            tab_str = tab * tab_level
-                            for f in me_faces:
-                                uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]
-
-                                if linebreaksinlists:
-                                    file.write(",\n")
-                                    file.write(
-                                        tab_str
-                                        + "<%d,%d,%d>"
-                                        % (
-                                            uniqueUVs[uvs[0]][0],
-                                            uniqueUVs[uvs[1]][0],
-                                            uniqueUVs[uvs[2]][0],
-                                        )
-                                    )
-                                else:
-                                    file.write(", ")
-                                    file.write(
-                                        "<%d,%d,%d>"
-                                        % (
-                                            uniqueUVs[uvs[0]][0],
-                                            uniqueUVs[uvs[1]][0],
-                                            uniqueUVs[uvs[2]][0],
-                                        )
-                                    )
-
-                            file.write("\n")
-                            tab_write("}\n")
-
-                        # XXX BOOLEAN
-                        write_object_csg_inside_vector(ob, file)
-                        if me.materials:
-                            try:
-                                material = me.materials[0]  # dodgy
-                                write_object_material_interior(material, ob, tab_write)
-                            except IndexError:
-                                print(me)
-
-                        # POV object modifiers such as
-                        # hollow / sturm / double_illuminate etc.
-                        write_object_modifiers(ob, file)
-
-                        # Importance for radiosity sampling added here:
-                        tab_write("radiosity { \n")
-                        tab_write("importance %3g \n" % importance)
-                        tab_write("}\n")
-
-                        tab_write("}\n")  # End of mesh block
-
-                    ob_eval.to_mesh_clear()
-                    continue
-
-                # ------------ Povray Primitives ------------ #
-                #  Also implicit elif (continue) clauses and sorted after mesh
-                #  as less often used.
-                if ob.pov.object_as == 'PLANE':
-                    tab_write("#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    # tab_write("rotate x*90\n")
-                    tab_write("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-
-                if ob.pov.object_as == 'SPHERE':
-
-                    tab_write(
-                        "#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    # tab_write("rotate x*90\n")
-                    tab_write("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-
-                if ob.pov.object_as == 'BOX':
-                    tab_write("#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    # tab_write("rotate x*90\n")
-                    tab_write("}\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(
-                        "#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    # tab_write("rotate x*90\n")
-                    tab_write("}\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(
-                        "#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    # cylinders written at origin, translated below
-                    write_object_modifiers(ob, file)
-                    # tab_write("rotate x*90\n")
-                    tab_write("}\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('#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    tab_write("rotate x*90\n")
-                    tab_write("translate <-0.5,0.5,0>\n")
-                    tab_write("scale <0,-1,0>\n")
-                    tab_write("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-
-                if ob.pov.object_as == 'TORUS':
-                    tab_write(
-                        "#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    tab_write("rotate x*90\n")
-                    tab_write("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-
-                if ob.pov.object_as == 'PARAMETRIC':
-                    tab_write("#declare %s = parametric {\n" % povdataname)
-                    tab_write("function { %s }\n" % ob.pov.x_eq)
-                    tab_write("function { %s }\n" % ob.pov.y_eq)
-                    tab_write("function { %s }\n" % ob.pov.z_eq)
-                    tab_write(
-                        "<%.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("max_gradient 0.001\n")
-                    if ob.pov.contained_by == "sphere":
-                        tab_write("contained_by { sphere{0, 2} }\n")
-                    else:
-                        tab_write("contained_by { box{-2, 2} }\n")
-                    tab_write("max_gradient %.6f\n" % ob.pov.max_gradient)
-                    tab_write("accuracy %.6f\n" % ob.pov.accuracy)
-                    tab_write("precompute 10 x,y,z\n")
-                    tab_write("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-
-                if ob.pov.object_as == 'ISOSURFACE_NODE':
-                    tab_write("#declare %s = isosurface{ \n" % povdataname)
-                    tab_write("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(
-                                            "#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("%s\n" % line.body)
-                    else:
-                        tab_write("abs(x) - 2 + y")
-                    tab_write("}\n")
-                    tab_write("threshold %.6g\n" % ob.pov.threshold)
-                    tab_write("max_gradient %.6g\n" % ob.pov.max_gradient)
-                    tab_write("accuracy  %.6g\n" % ob.pov.accuracy)
-                    tab_write("contained_by { ")
-                    if ob.pov.contained_by == "sphere":
-                        tab_write("sphere {0,%.6g}}\n" % ob.pov.container_scale)
-                    else:
-                        tab_write(
-                            "box {-%.6g,%.6g}}\n" % (ob.pov.container_scale, ob.pov.container_scale)
-                        )
-                    if ob.pov.all_intersections:
-                        tab_write("all_intersections\n")
-                    else:
-                        if ob.pov.max_trace > 1:
-                            tab_write("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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    tab_write("scale %.6g\n" % (1 / ob.pov.container_scale))
-                    tab_write("}\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("#declare %s = isosurface{ \n" % povdataname)
-                        tab_write("function{ \n")
-                        tab_write(simple_isosurface_function)
-                        tab_write("}\n")
-                        tab_write("threshold %.6g\n" % ob.pov.threshold)
-                        tab_write("max_gradient %.6g\n" % ob.pov.max_gradient)
-                        tab_write("accuracy  %.6g\n" % ob.pov.accuracy)
-                        tab_write("contained_by { ")
-                        if ob.pov.contained_by == "sphere":
-                            tab_write("sphere {0,%.6g}}\n" % ob.pov.container_scale)
-                        else:
-                            tab_write(
-                                "box {-%.6g,%.6g}}\n" % (ob.pov.container_scale, ob.pov.container_scale)
-                            )
-                        if ob.pov.all_intersections:
-                            tab_write("all_intersections\n")
-                        else:
-                            if ob.pov.max_trace > 1:
-                                tab_write("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(material, ob, tab_write)
-                            except IndexError:
-                                print(me)
-                        # tab_write("texture {%s}\n"%pov_mat_name)
-                        tab_write("scale %.6g\n" % (1 / ob.pov.container_scale))
-                        tab_write("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-
-                if ob.pov.object_as == 'SUPERELLIPSOID':
-                    tab_write(
-                        "#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    tab_write("}\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(
-                        "#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(material, ob, tab_write)
-                        except IndexError:
-                            print(me)
-                    # tab_write("texture {%s}\n"%pov_mat_name)
-                    write_object_modifiers(ob, file)
-                    tab_write("rotate x*90\n")
-                    tab_write("}\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(
-                        "#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("}\n")
-                    continue  # Don't render proxy mesh, skip to next object
-    if csg:
-        duplidata_ref = []
-        _dupnames_seen = dict()  # avoid duplicate output during introspection
-        for ob in sel:
-            # matrix = global_matrix @ obj.matrix_world
-            if ob.is_instancer:
-                tab_write("\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 not in {'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(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("\n//----Blender Object Name:%s----\n" % ob_name)
-                    if ob.pov.object_as == '':
-                        tab_write("object { \n")
-                        tab_write("%s\n" % data_name)
-                        tab_write("%s\n" % matrix_str)
-                        tab_write("}\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("%s { \n" % operation)
-                                tab_write("object { \n")
-                                tab_write("%s\n" % data_name)
-                                tab_write("%s\n" % matrix_str)
-                                tab_write("}\n")
-                                tab_write("object { \n")
-                                tab_write("%s\n" % ('DATA' + mod_ob_name))
-                                tab_write("%s\n" % mod_ob_matrix)
-                                tab_write("}\n")
-                                tab_write("}\n")
-                                break
-                        if no_boolean:
-                            tab_write("object { \n")
-                            tab_write("%s\n" % data_name)
-                            tab_write("%s\n" % matrix_str)
-                            tab_write("}\n")
diff --git a/render_povray/object_particles.py b/render_povray/particles.py
old mode 100755
new mode 100644
similarity index 61%
rename from render_povray/object_particles.py
rename to render_povray/particles.py
index 66477485b..4ca09471a
--- a/render_povray/object_particles.py
+++ b/render_povray/particles.py
@@ -4,12 +4,14 @@
 """Get some Blender particle objects translated to POV."""
 
 import bpy
+
 import random
 
 
 def pixel_relative_guess(ob):
     """Convert some object x dimension to a rough pixel relative order of magnitude"""
     from bpy_extras import object_utils
+
     scene = bpy.context.scene
     cam = scene.camera
     render = scene.render
@@ -27,23 +29,25 @@ def pixel_relative_guess(ob):
     return apparent_size / pixel_pitch_x
 
 
-def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
+def export_hair(file, ob, mod, p_sys, global_matrix):
     """Get Blender path particles (hair strands) objects translated to POV sphere_sweep unions."""
     # tstart = time.time()
+    from .render import write_matrix
+
     textured_hair = 0
-    if ob.material_slots[p_sys.settings.material - 1].material and ob.active_material is not None:
-        pmaterial = ob.material_slots[p_sys.settings.material - 1].material
+    depsgraph = bpy.context.evaluated_depsgraph_get()
+    p_sys_settings = p_sys.settings.evaluated_get(depsgraph)
+    if ob.material_slots[p_sys_settings.material - 1].material and ob.active_material is not None:
+        pmaterial = ob.material_slots[p_sys_settings.material - 1].material
         # XXX Todo: replace by pov_(Particles?)_texture_slot
         for th in pmaterial.pov_texture_slots:
             povtex = th.texture  # slot.name
             tex = bpy.data.textures[povtex]
 
             if (
-                th
+                tex
                 and th.use
-                and (
-                    (tex.type == 'IMAGE' and tex.image) or tex.type != 'IMAGE'
-                )
+                and ((tex.type == "IMAGE" and tex.image) or tex.type != "IMAGE")
                 and th.use_map_color_diffuse
             ):
                 textured_hair = 1
@@ -74,12 +78,12 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
     # In the viewport it will be at viewport resolution.
     # So there is no need fo render engines to use this function anymore,
     # it's automatic now.
-    steps = p_sys.settings.display_step
-    steps = 2 ** steps  # or + 1 # Formerly : len(particle.hair_keys)
+    steps = p_sys_settings.display_step
+    steps = 2**steps  # or + 1 # Formerly : len(particle.hair_keys)
 
-    total_number_of_strands = p_sys.settings.count + p_sys.settings.rendered_child_count
+    total_number_of_strands = p_sys_settings.count * p_sys_settings.rendered_child_count
     # hairCounter = 0
-    file.write('#declare HairArray = array[%i] {\n' % total_number_of_strands)
+    file.write("#declare HairArray = array[%i] {\n" % total_number_of_strands)
     for pindex in range(total_number_of_strands):
 
         # if particle.is_exist and particle.is_visible:
@@ -87,32 +91,33 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
         # controlPointCounter = 0
         # Each hair is represented as a separate sphere_sweep in POV-Ray.
 
-        file.write('sphere_sweep{')
-        if p_sys.settings.use_hair_bspline:
-            file.write('b_spline ')
+        file.write("sphere_sweep{")
+        if p_sys_settings.use_hair_bspline:
+            file.write("b_spline ")
             file.write(
-                '%i,\n' % (steps + 2)
+                "%i,\n" % (steps + 2)
             )  # +2 because the first point needs tripling to be more than a handle in POV
         else:
-            file.write('linear_spline ')
-            file.write('%i,\n' % steps)
+            file.write("linear_spline ")
+            file.write("%i,\n" % steps)
         # changing world coordinates to object local coordinates by
         # multiplying with inverted matrix
         init_coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=0))
+        init_coord = (init_coord[0], init_coord[1], init_coord[2])
         if (
-            ob.material_slots[p_sys.settings.material - 1].material
+            ob.material_slots[p_sys_settings.material - 1].material
             and ob.active_material is not None
         ):
-            pmaterial = ob.material_slots[p_sys.settings.material - 1].material
+            pmaterial = ob.material_slots[p_sys_settings.material - 1].material
             for th in pmaterial.pov_texture_slots:
-                if th and th.use and th.use_map_color_diffuse:
-                    povtex = th.texture  # slot.name
-                    tex = bpy.data.textures[povtex]
+                povtex = th.texture  # slot.name
+                tex = bpy.data.textures[povtex]
+                if tex and th.use and th.use_map_color_diffuse:
                     # treat POV textures as bitmaps
                     if (
-                        tex.type == 'IMAGE'
+                        tex.type == "IMAGE"
                         and tex.image
-                        and th.texture_coords == 'UV'
+                        and th.texture_coords == "UV"
                         and ob.data.uv_textures is not None
                     ):
                         # or (
@@ -135,36 +140,36 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
                         init_color = (r, g, b, a)
                     else:
                         # only overwrite variable for each competing texture for now
-                        init_color = tex.evaluate((init_coord[0], init_coord[1], init_coord[2]))
+                        init_color = tex.evaluate(init_coord)
         for step in range(steps):
             coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=step))
             # for controlPoint in particle.hair_keys:
-            if p_sys.settings.clump_factor != 0:
-                hair_strand_diameter = p_sys.settings.clump_factor / 200.0 * random.uniform(0.5, 1)
+            if p_sys_settings.clump_factor:
+                hair_strand_diameter = p_sys_settings.clump_factor / 200.0 * random.uniform(0.5, 1)
             elif step == 0:
                 hair_strand_diameter = strand_start
             else:
                 # still initialize variable
                 hair_strand_diameter = strand_start
-                if strand_shape != 0.0:
-                    if strand_shape < 0.0:
-                        fac = pow(step, (1.0 + strand_shape))
-                    else:
-                        fac = pow(step, (1.0 / (1.0 - strand_shape)))
-                else:
+                if strand_shape == 0.0:
                     fac = step
-                hair_strand_diameter += fac * (strand_end - strand_start) / (
-                    p_sys.settings.display_step + 1
+                elif strand_shape < 0:
+                    fac = pow(step, (1.0 + strand_shape))
+                else:
+                    fac = pow(step, (1.0 / (1.0 - strand_shape)))
+                hair_strand_diameter += (
+                    fac * (strand_end - strand_start) / (p_sys_settings.display_step + 1)
                 )  # XXX +1 or -1 or nothing ?
-            if step == 0 and p_sys.settings.use_hair_bspline:
+            abs_hair_strand_diameter = abs(hair_strand_diameter)
+            if step == 0 and p_sys_settings.use_hair_bspline:
                 # Write three times the first point to compensate pov Bezier handling
                 file.write(
-                    '<%.6g,%.6g,%.6g>,%.7g,\n'
-                    % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
+                    "<%.6g,%.6g,%.6g>,%.7g,\n"
+                    % (coord[0], coord[1], coord[2], abs_hair_strand_diameter)
                 )
                 file.write(
-                    '<%.6g,%.6g,%.6g>,%.7g,\n'
-                    % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
+                    "<%.6g,%.6g,%.6g>,%.7g,\n"
+                    % (coord[0], coord[1], coord[2], abs_hair_strand_diameter)
                 )
                 # Useless because particle location is the tip, not the root:
                 # file.write(
@@ -173,7 +178,7 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
                 # particle.location[0],
                 # particle.location[1],
                 # particle.location[2],
-                # abs(hair_strand_diameter)
+                # abs_hair_strand_diameter
                 # )
                 # )
                 # file.write(',\n')
@@ -183,7 +188,7 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
             # Each control point is written out, along with the radius of the
             # hair at that point.
             file.write(
-                '<%.6g,%.6g,%.6g>,%.7g' % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
+                "<%.6g,%.6g,%.6g>,%.7g" % (coord[0], coord[1], coord[2], abs_hair_strand_diameter)
             )
 
             # All coordinates except the last need a following comma.
@@ -193,32 +198,32 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
                     # Write pigment and alpha (between Pov and Blender,
                     # alpha 0 and 1 are reversed)
                     file.write(
-                        '\npigment{ color srgbf < %.3g, %.3g, %.3g, %.3g> }\n'
+                        "\npigment{ color srgbf < %.3g, %.3g, %.3g, %.3g> }\n"
                         % (init_color[0], init_color[1], init_color[2], 1.0 - init_color[3])
                     )
                 # End the sphere_sweep declaration for this hair
-                file.write('}\n')
+                file.write("}\n")
 
             else:
-                file.write(',\n')
+                file.write(",\n")
         # All but the final sphere_sweep (each array element) needs a terminating comma.
         if pindex != total_number_of_strands:
-            file.write(',\n')
+            file.write(",\n")
         else:
-            file.write('\n')
+            file.write("\n")
 
     # End the array declaration.
 
-    file.write('}\n')
-    file.write('\n')
+    file.write("}\n")
+    file.write("\n")
 
     if not textured_hair:
         # Pick up the hair material diffuse color and create a default POV-Ray hair texture.
 
-        file.write('#ifndef (HairTexture)\n')
-        file.write('  #declare HairTexture = texture {\n')
+        file.write("#ifndef (HairTexture)\n")
+        file.write("  #declare HairTexture = texture {\n")
         file.write(
-            '    pigment {srgbt <%s,%s,%s,%s>}\n'
+            "    pigment {srgbt <%s,%s,%s,%s>}\n"
             % (
                 pmaterial.diffuse_color[0],
                 pmaterial.diffuse_color[1],
@@ -226,56 +231,48 @@ def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
                 (pmaterial.strand.width_fade + 0.05),
             )
         )
-        file.write('  }\n')
-        file.write('#end\n')
-        file.write('\n')
+        file.write("  }\n")
+        file.write("#end\n")
+        file.write("\n")
 
     # Dynamically create a union of the hairstrands (or a subset of them).
     # By default use every hairstrand, commented line is for hand tweaking test renders.
-    file.write('//Increasing HairStep divides the amount of hair for test renders.\n')
-    file.write('#ifndef(HairStep) #declare HairStep = 1; #end\n')
-    file.write('union{\n')
-    file.write('  #local I = 0;\n')
-    file.write('  #while (I < %i)\n' % total_number_of_strands)
-    file.write('    object {HairArray[I]')
+    file.write("//Increasing HairStep divides the amount of hair for test renders.\n")
+    file.write("#ifndef(HairStep) #declare HairStep = 1; #end\n")
+    file.write("union{\n")
+    file.write("  #local I = 0;\n")
+    file.write("  #while (I < %i)\n" % total_number_of_strands)
+    file.write("    object {HairArray[I]")
     if textured_hair:
-        file.write('\n')
+        file.write("\n")
     else:
-        file.write(' texture{HairTexture}\n')
+        file.write(" texture{HairTexture}\n")
     # Translucency of the hair:
-    file.write('        hollow\n')
-    file.write('        double_illuminate\n')
-    file.write('        interior {\n')
-    file.write('            ior 1.45\n')
-    file.write('            media {\n')
-    file.write('                scattering { 1, 10*<0.73, 0.35, 0.15> /*extinction 0*/ }\n')
-    file.write('                absorption 10/<0.83, 0.75, 0.15>\n')
-    file.write('                samples 1\n')
-    file.write('                method 2\n')
-    file.write('                density {cylindrical\n')
-    file.write('                    color_map {\n')
-    file.write('                        [0.0 rgb <0.83, 0.45, 0.35>]\n')
-    file.write('                        [0.5 rgb <0.8, 0.8, 0.4>]\n')
-    file.write('                        [1.0 rgb <1,1,1>]\n')
-    file.write('                    }\n')
-    file.write('                }\n')
-    file.write('            }\n')
-    file.write('        }\n')
-    file.write('    }\n')
+    file.write("        hollow\n")
+    file.write("        double_illuminate\n")
+    file.write("        interior {\n")
+    file.write("            ior 1.45\n")
+    file.write("            media {\n")
+    file.write("                scattering { 1, 10*<0.73, 0.35, 0.15> /*extinction 0*/ }\n")
+    file.write("                absorption 10/<0.83, 0.75, 0.15>\n")
+    file.write("                samples 1\n")
+    file.write("                method 2\n")
+    file.write("                density {cylindrical\n")
+    file.write("                    color_map {\n")
+    file.write("                        [0.0 rgb <0.83, 0.45, 0.35>]\n")
+    file.write("                        [0.5 rgb <0.8, 0.8, 0.4>]\n")
+    file.write("                        [1.0 rgb <1,1,1>]\n")
+    file.write("                    }\n")
+    file.write("                }\n")
+    file.write("            }\n")
+    file.write("        }\n")
+    file.write("    }\n")
 
-    file.write('    #local I = I + HairStep;\n')
-    file.write('  #end\n')
+    file.write("    #local I = I + HairStep;\n")
+    file.write("  #end\n")
 
-    write_matrix(global_matrix @ ob.matrix_world)
+    write_matrix(file, global_matrix @ ob.matrix_world)
 
-    file.write('}')
+    file.write("}")
     print("Totals hairstrands written: %i" % total_number_of_strands)
-    print("Number of tufts (particle systems)", len(ob.particle_systems))
-
-    # Set back the displayed number of particles to preview count
-    # p_sys.set_resolution(scene, ob, 'PREVIEW') #DEPRECATED
-    # When you render, the entire dependency graph will be
-    # evaluated at render resolution, including the particles.
-    # In the viewport it will be at viewport resolution.
-    # So there is no need fo render engines to use this function anymore,
-    # it's automatic now.
+    print("Number of tufts (particle systems): %i" % len(ob.particle_systems))
diff --git a/render_povray/particles_properties.py b/render_povray/particles_properties.py
new file mode 100644
index 000000000..573ca48cd
--- /dev/null
+++ b/render_povray/particles_properties.py
@@ -0,0 +1,718 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+"""Declare shading properties exported to POV textures."""
+import bpy
+from bpy.utils import register_class, unregister_class
+from bpy.types import PropertyGroup
+from bpy.props import (
+    StringProperty,
+    BoolProperty,
+    FloatProperty,
+    PointerProperty,
+)
+
+
+class MaterialStrandSettings(PropertyGroup):
+    """Declare strand properties controllable in UI and translated to POV."""
+
+    bl_description = ("Strand settings for the material",)
+
+    blend_distance: FloatProperty(
+        name="Distance",
+        description="Worldspace distance over which to blend in the surface normal",
+        min=0.0,
+        max=10.0,
+        soft_min=0.0,
+        soft_max=10.0,
+        default=0.0,
+        precision=3,
+    )
+
+    root_size: FloatProperty(
+        name="Root",
+        description="Start size of strands in pixels or Blender units",
+        min=0.25,
+        default=1.0,
+        precision=5,
+    )
+
+    shape: FloatProperty(
+        name="Shape",
+        description="Positive values make strands rounder, negative ones make strands spiky",
+        min=-0.9,
+        max=0.9,
+        default=0.0,
+        precision=3,
+    )
+
+    size_min: FloatProperty(
+        name="Minimum",
+        description="Minimum size of strands in pixels",
+        min=0.001,
+        max=10.0,
+        default=1.0,
+        precision=3,
+    )
+
+    tip_size: FloatProperty(
+        name="Tip",
+        description="End size of strands in pixels or Blender units",
+        min=0.0,
+        default=1.0,
+        precision=5,
+    )
+
+    use_blender_units: BoolProperty(
+        name="Blender Units",
+        description="Use Blender units for widths instead of pixels",
+        default=False,
+    )
+
+    use_surface_diffuse: BoolProperty(
+        name="Surface diffuse",
+        description="Make diffuse shading more similar to shading the surface",
+        default=False,
+    )
+
+    use_tangent_shading: BoolProperty(
+        name="Tangent Shading",
+        description="Use direction of strands as normal for tangent-shading",
+        default=True,
+    )
+
+    uv_layer: StringProperty(
+        name="UV Layer",
+        # icon="GROUP_UVS",
+        description="Name of UV map to override",
+        default="",
+    )
+
+    width_fade: FloatProperty(
+        name="Width Fade",
+        description="Transparency along the width of the strand",
+        min=0.0,
+        max=2.0,
+        default=0.0,
+        precision=3,
+    )
+
+    # halo
+
+    # Halo settings for the material
+    # Type: MaterialHalo, (readonly, never None)
+
+
+    # darkness
+
+    # Minnaert darkness
+    # Type: float in [0, 2], default 0.0
+
+    # diffuse_color
+
+    # Diffuse color of the material
+    # Type: float array of 3 items in [0, inf], default (0.0, 0.0, 0.0)
+
+    # diffuse_fresnel
+
+    # Power of Fresnel
+    # Type: float in [0, 5], default 0.0
+
+    # diffuse_fresnel_factor
+
+    # Blending factor of Fresnel
+    # Type: float in [0, 5], default 0.0
+
+    # diffuse_intensity
+
+    # Amount of diffuse reflection
+    # Type: float in [0, 1], default 0.0
+
+    # diffuse_ramp
+
+    # Color ramp used to affect diffuse shading
+    # Type: ColorRamp, (readonly)
+
+    # diffuse_ramp_blend
+
+    # Blending method of the ramp and the diffuse color
+    # Type: enum in [‘MIX’, ‘ADD’, ‘MULTIPLY’, ‘SUBTRACT’, ‘SCREEN’, ‘DIVIDE’, ‘DIFFERENCE’, ‘DARKEN’, ‘LIGHTEN’, ‘OVERLAY’, ‘DODGE’, ‘BURN’, ‘HUE’, ‘SATURATION’, ‘VALUE’, ‘COLOR’, ‘SOFT_LIGHT’, ‘LINEAR_LIGHT’], default ‘MIX’
+
+    # diffuse_ramp_factor
+
+    # Blending factor (also uses alpha in Colorband)
+    # Type: float in [0, 1], default 0.0
+
+    # diffuse_ramp_input
+
+    # How the ramp maps on the surface
+    # Type: enum in [‘SHADER’, ‘ENERGY’, ‘NORMAL’, ‘RESULT’], default ‘SHADER’
+
+    # diffuse_shader
+
+    # LAMBERT Lambert, Use a Lambertian shader.
+    # OREN_NAYAR Oren-Nayar, Use an Oren-Nayar shader.
+    # TOON Toon, Use a toon shader.
+    # MINNAERT Minnaert, Use a Minnaert shader.
+    # FRESNEL Fresnel, Use a Fresnel shader.
+
+    # Type: enum in [‘LAMBERT’, ‘OREN_NAYAR’, ‘TOON’, ‘MINNAERT’, ‘FRESNEL’], default ‘LAMBERT’
+
+    # diffuse_toon_size
+
+    # Size of diffuse toon area
+    # Type: float in [0, 3.14], default 0.0
+
+    # diffuse_toon_smooth
+
+    # Smoothness of diffuse toon area
+    # Type: float in [0, 1], default 0.0
+
+    # emit
+
+    # Amount of light to emit
+    # Type: float in [0, inf], default 0.0
+
+    # halo
+
+    # Halo settings for the material
+    # Type: MaterialHalo, (readonly, never None)
+
+    # invert_z
+
+    # Render material’s faces with an inverted Z buffer (scanline only)
+    # Type: boolean, default False
+
+    # light_group
+
+    # Limit lighting to lamps in this Group
+    # Type: Group
+
+    # line_color
+
+    # Line color used for Freestyle line rendering
+    # Type: float array of 4 items in [0, inf], default (0.0, 0.0, 0.0, 0.0)
+
+    # line_priority
+
+    # The line color of a higher priority is used at material boundaries
+    # Type: int in [0, 32767], default 0
+
+    # mirror_color
+
+    # Mirror color of the material
+    # Type: float array of 3 items in [0, inf], default (0.0, 0.0, 0.0)
+
+    # node_tree
+
+    # Node tree for node based materials
+    # Type: NodeTree, (readonly)
+
+    # offset_z
+
+    # Give faces an artificial offset in the Z buffer for Z transparency
+    # Type: float in [-inf, inf], default 0.0
+
+    # paint_active_slot
+
+    # Index of active texture paint slot
+    # Type: int in [0, 32767], default 0
+
+    # paint_clone_slot
+
+    # Index of clone texture paint slot
+    # Type: int in [0, 32767], default 0
+
+    # pass_index
+
+    # Index number for the “Material Index” render pass
+    # Type: int in [0, 32767], default 0
+
+    # physics
+
+    # Game physics settings
+    # Type: MaterialPhysics, (readonly, never None)
+
+    # preview_render_type
+
+    # Type of preview render
+
+    # FLAT Flat, Flat XY plane.
+    # SPHERE Sphere, Sphere.
+    # CUBE Cube, Cube.
+    # MONKEY Monkey, Monkey.
+    # HAIR Hair, Hair strands.
+    # SPHERE_A World Sphere, Large sphere with sky.
+
+    # Type: enum in [‘FLAT’, ‘SPHERE’, ‘CUBE’, ‘MONKEY’, ‘HAIR’, ‘SPHERE_A’], default ‘FLAT’
+
+    # roughness
+
+    # Oren-Nayar Roughness
+    # Type: float in [0, 3.14], default 0.0
+
+    # shadow_buffer_bias
+
+    # Factor to multiply shadow buffer bias with (0 is ignore)
+    # Type: float in [0, 10], default 0.0
+
+    # shadow_cast_alpha
+
+    # Shadow casting alpha, in use for Irregular and Deep shadow buffer
+    # Type: float in [0.001, 1], default 0.0
+
+    # shadow_only_type
+
+    # How to draw shadows
+
+    # SHADOW_ONLY_OLD Shadow and Distance, Old shadow only method.
+    # SHADOW_ONLY Shadow Only, Improved shadow only method.
+    # SHADOW_ONLY_SHADED Shadow and Shading, Improved shadow only method which also renders lightless areas as shadows.
+
+    # Type: enum in [‘SHADOW_ONLY_OLD’, ‘SHADOW_ONLY’, ‘SHADOW_ONLY_SHADED’], default ‘SHADOW_ONLY_OLD’
+
+    # shadow_ray_bias
+
+    # Shadow raytracing bias to prevent terminator problems on shadow boundary
+    # Type: float in [0, 0.25], default 0.0
+
+    # specular_color
+
+    # Specular color of the material
+    # Type: float array of 3 items in [0, inf], default (0.0, 0.0, 0.0)
+
+    # specular_hardness
+
+    # How hard (sharp) the specular reflection is
+    # Type: int in [1, 511], default 0
+
+    # specular_intensity
+
+    # How intense (bright) the specular reflection is
+    # Type: float in [0, 1], default 0.0
+
+    # specular_ior
+
+    # Specular index of refraction
+    # Type: float in [1, 10], default 0.0
+
+    # specular_ramp
+
+    # Color ramp used to affect specular shading
+    # Type: ColorRamp, (readonly)
+
+    # specular_ramp_blend
+
+    # Blending method of the ramp and the specular color
+    # Type: enum in [‘MIX’, ‘ADD’, ‘MULTIPLY’, ‘SUBTRACT’, ‘SCREEN’, ‘DIVIDE’, ‘DIFFERENCE’, ‘DARKEN’, ‘LIGHTEN’, ‘OVERLAY’, ‘DODGE’, ‘BURN’, ‘HUE’, ‘SATURATION’, ‘VALUE’, ‘COLOR’, ‘SOFT_LIGHT’, ‘LINEAR_LIGHT’], default ‘MIX’
+
+    # specular_ramp_factor
+
+    # Blending factor (also uses alpha in Colorband)
+    # Type: float in [0, 1], default 0.0
+
+    # specular_ramp_input
+
+    # How the ramp maps on the surface
+    # Type: enum in [‘SHADER’, ‘ENERGY’, ‘NORMAL’, ‘RESULT’], default ‘SHADER’
+    # specular_shader
+
+    # COOKTORR CookTorr, Use a Cook-Torrance shader.
+    # PHONG Phong, Use a Phong shader.
+    # BLINN Blinn, Use a Blinn shader.
+    # TOON Toon, Use a toon shader.
+    # WARDISO WardIso, Use a Ward anisotropic shader.
+
+    # Type: enum in [‘COOKTORR’, ‘PHONG’, ‘BLINN’, ‘TOON’, ‘WARDISO’], default ‘COOKTORR’
+
+    # specular_slope
+
+    # The standard deviation of surface slope
+    # Type: float in [0, 0.4], default 0.0
+
+    # specular_toon_size
+
+    # Size of specular toon area
+    # Type: float in [0, 1.53], default 0.0
+
+    # specular_toon_smooth
+
+    # Smoothness of specular toon area
+    # Type: float in [0, 1], default 0.0
+
+    # strand
+
+    # Strand settings for the material
+    # Type: MaterialStrand, (readonly, never None)
+
+    # subsurface_scattering
+
+    # Subsurface scattering settings for the material
+    # Type: MaterialSubsurfaceScattering, (readonly, never None)
+
+    # texture_paint_images
+
+    # Texture images used for texture painting
+    # Type: bpy_prop_collection of Image, (readonly)
+
+    # texture_paint_slots
+
+    # Texture slots defining the mapping and influence of textures
+    # Type: bpy_prop_collection of TexPaintSlot, (readonly)
+
+    # texture_slots
+
+    # Texture slots defining the mapping and influence of textures
+    # Type: MaterialTextureSlots bpy_prop_collection of MaterialTextureSlot, (readonly)
+
+    # type
+
+    # Material type defining how the object is rendered
+
+    # SURFACE Surface, Render object as a surface.
+    # WIRE Wire, Render the edges of faces as wires (not supported in raytracing).
+    # VOLUME Volume, Render object as a volume.
+    # HALO Halo, Render object as halo particles.
+
+    # Type: enum in [‘SURFACE’, ‘WIRE’, ‘VOLUME’, ‘HALO’], default ‘SURFACE’
+
+    # use_cast_shadows
+
+    # Allow this material to cast shadows
+    # Type: boolean, default False
+
+    # use_cast_shadows_only
+
+    # Make objects with this material appear invisible (not rendered), only casting shadows
+    # Type: boolean, default False
+
+    # use_cubic
+
+    # Use cubic interpolation for diffuse values, for smoother transitions
+    # Type: boolean, default False
+
+    # use_diffuse_ramp
+
+    # Toggle diffuse ramp operations
+    # Type: boolean, default False
+
+    # use_face_texture
+
+    # Replace the object’s base color with color from UV map image textures
+    # Type: boolean, default False
+
+    # use_face_texture_alpha
+
+    # Replace the object’s base alpha value with alpha from UV map image textures
+    # Type: boolean, default False
+
+    # use_full_oversampling
+
+    # Force this material to render full shading/textures for all anti-aliasing samples
+    # Type: boolean, default False
+
+    # use_light_group_exclusive
+
+    # Material uses the light group exclusively - these lamps are excluded from other scene lighting
+    # Type: boolean, default False
+
+    # use_light_group_local
+
+    # When linked in, material uses local light group with the same name
+    # Type: boolean, default False
+
+    # use_mist
+
+    # Use mist with this material (in world settings)
+    # Type: boolean, default False
+
+    # use_nodes
+
+    # Use shader nodes to render the material
+    # Type: boolean, default False
+
+    # use_object_color
+
+    # Modulate the result with a per-object color
+    # Type: boolean, default False
+
+    # use_only_shadow
+
+    # Render shadows as the material’s alpha value, making the material transparent except for shadowed areas
+    # Type: boolean, default False
+
+    # use_ray_shadow_bias
+
+    # Prevent raytraced shadow errors on surfaces with smooth shaded normals (terminator problem)
+    # Type: boolean, default False
+
+    # use_raytrace
+
+    # Include this material and geometry that uses it in raytracing calculations
+    # Type: boolean, default False
+
+    # use_shadeless
+
+    # Make this material insensitive to light or shadow
+    # Type: boolean, default False
+
+    # use_shadows
+
+    # Allow this material to receive shadows
+    # Type: boolean, default False
+
+    # use_sky
+
+    # Render this material with zero alpha, with sky background in place (scanline only)
+    # Type: boolean, default False
+
+    # use_specular_ramp
+
+    # Toggle specular ramp operations
+    # Type: boolean, default False
+
+    # use_tangent_shading
+
+    # Use the material’s tangent vector instead of the normal for shading - for anisotropic shading effects
+    # Type: boolean, default False
+
+    # use_textures
+
+    # Enable/Disable each texture
+    # Type: boolean array of 18 items, default (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False)
+
+    # use_transparency
+
+    # Render material as transparent
+    # Type: boolean, default False
+
+    # use_transparent_shadows
+
+    # Allow this object to receive transparent shadows cast through other objects
+    # Type: boolean, default False
+
+    # use_uv_project
+
+    # Use to ensure UV interpolation is correct for camera projections (use with UV project modifier)
+    # Type: boolean, default False
+
+    # use_vertex_color_light
+
+    # Add vertex colors as additional lighting
+    # Type: boolean, default False
+
+    # use_vertex_color_paint
+
+    # Replace object base color with vertex colors (multiply with ‘texture face’ face assigned textures)
+    # Type: boolean, default False
+
+    # volume
+
+    # Volume settings for the material
+    # Type: MaterialVolume, (readonly, never None)
+    """
+    (mat.type in {'SURFACE', 'WIRE', 'VOLUME'})
+     "use_transparency")
+
+
+
+    mat.use_transparency and mat.pov.transparency_method == 'Z_TRANSPARENCY'
+
+
+
+
+            col.prop(mat, "use_raytrace")
+            col.prop(mat, "use_full_oversampling")
+
+            sub.prop(mat, "use_sky")
+
+
+            col.prop(mat, "use_cast_shadows", text="Cast")
+            col.prop(mat, "use_cast_shadows_only", text="Cast Only")
+            col.prop(mat, "use_cast_buffer_shadows")
+
+            sub.active = mat.use_cast_buffer_shadows
+            sub.prop(mat, "shadow_cast_alpha", text="Casting Alpha")
+            col.prop(mat, "use_cast_approximate")
+
+
+
+            col.prop(mat, "diffuse_color", text="")
+
+            sub.active = (not mat.use_shadeless)
+
+            sub.prop(mat, "diffuse_intensity", text="Intensity")
+
+
+            col.prop(mat, "diffuse_shader", text="")
+            col.prop(mat, "use_diffuse_ramp", text="Ramp")
+
+
+            if mat.diffuse_shader == 'OREN_NAYAR':
+                col.prop(mat, "roughness")
+            elif mat.diffuse_shader == 'MINNAERT':
+                col.prop(mat, "darkness")
+            elif mat.diffuse_shader == 'TOON':
+
+                row.prop(mat, "diffuse_toon_size", text="Size")
+                row.prop(mat, "diffuse_toon_smooth", text="Smooth")
+            elif mat.diffuse_shader == 'FRESNEL':
+
+                row.prop(mat, "diffuse_fresnel", text="Fresnel")
+                row.prop(mat, "diffuse_fresnel_factor", text="Factor")
+
+            if mat.use_diffuse_ramp:
+
+                col.template_color_ramp(mat, "diffuse_ramp", expand=True)
+
+
+
+                row.prop(mat, "diffuse_ramp_input", text="Input")
+                row.prop(mat, "diffuse_ramp_blend", text="Blend")
+
+                col.prop(mat, "diffuse_ramp_factor", text="Factor")
+
+
+
+
+            col.prop(mat, "specular_color", text="")
+            col.prop(mat, "specular_intensity", text="Intensity")
+
+            col.prop(mat, "specular_shader", text="")
+            col.prop(mat, "use_specular_ramp", text="Ramp")
+
+            if mat.pov.specular_shader in {'COOKTORR', 'PHONG'}:
+                col.prop(mat, "specular_hardness", text="Hardness")
+            elif mat.pov.specular_shader == 'BLINN':
+
+                row.prop(mat, "specular_hardness", text="Hardness")
+                row.prop(mat, "specular_ior", text="IOR")
+            elif mat.pov.specular_shader == 'WARDISO':
+                col.prop(mat, "specular_slope", text="Slope")
+            elif mat.pov.specular_shader == 'TOON':
+
+                row.prop(mat, "specular_toon_size", text="Size")
+                row.prop(mat, "specular_toon_smooth", text="Smooth")
+
+            if mat.use_specular_ramp:
+                layout.separator()
+                layout.template_color_ramp(mat, "specular_ramp", expand=True)
+                layout.separator()
+
+                row = layout.row()
+                row.prop(mat, "specular_ramp_input", text="Input")
+                row.prop(mat, "specular_ramp_blend", text="Blend")
+
+                layout.prop(mat, "specular_ramp_factor", text="Factor")
+
+
+    XXX remove unused props and relayout as done for transparent sky
+
+
+    class MATERIAL_PT_halo(MaterialButtonsPanel, Panel):
+        bl_label = "Halo"
+        COMPAT_ENGINES = {'BLENDER_RENDER'}
+
+        @classmethod
+        def poll(cls, context):
+            mat = context.material
+            engine = context.scene.render.engine
+            return mat and (mat.type == 'HALO') and (engine in cls.COMPAT_ENGINES)
+
+        def draw(self, context):
+            layout = self.layout
+
+            mat = context.material  # don't use node material
+            halo = mat.pov.halo
+
+            def number_but(layout, toggle, number, name, color):
+                row = layout.row(align=True)
+                row.prop(halo, toggle, text="")
+                sub = row.column(align=True)
+                sub.active = getattr(halo, toggle)
+                sub.prop(halo, number, text=name, translate=False)
+                if not color == "":
+                    sub.prop(mat, color, text="")
+
+            split = layout.split()
+
+            col = split.column()
+            col.prop(mat, "alpha")
+            col.prop(mat, "diffuse_color", text="")
+            col.prop(halo, "seed")
+
+            col = split.column()
+            col.prop(halo, "size")
+            col.prop(halo, "hardness")
+            col.prop(halo, "add")
+
+            layout.label(text="Options:")
+
+            split = layout.split()
+            col = split.column()
+            col.prop(halo, "use_texture")
+            col.prop(halo, "use_vertex_normal")
+            col.prop(halo, "use_extreme_alpha")
+            col.prop(halo, "use_shaded")
+            col.prop(halo, "use_soft")
+
+            col = split.column()
+            number_but(col, "use_ring", "ring_count", iface_("Rings"), "mirror_color")
+            number_but(col, "use_lines", "line_count", iface_("Lines"), "specular_color")
+            number_but(col, "use_star", "star_tip_count", iface_("Star Tips"), "")
+
+
+    class MATERIAL_PT_flare(MaterialButtonsPanel, Panel):
+        bl_label = "Flare"
+        COMPAT_ENGINES = {'BLENDER_RENDER'}
+
+        @classmethod
+        def poll(cls, context):
+            mat = context.material
+            engine = context.scene.render.engine
+            return mat and (mat.type == 'HALO') and (engine in cls.COMPAT_ENGINES)
+
+        def draw_header(self, context):
+            halo = context.material.pov.halo
+
+            self.layout.prop(halo, "use_flare_mode", text="")
+
+        def draw(self, context):
+            layout = self.layout
+
+            mat = context.material  # don't use node material
+            halo = mat.pov.halo
+
+            layout.active = halo.use_flare_mode
+
+            split = layout.split()
+
+            col = split.column()
+            col.prop(halo, "flare_size", text="Size")
+            col.prop(halo, "flare_boost", text="Boost")
+            col.prop(halo, "flare_seed", text="Seed")
+
+            col = split.column()
+            col.prop(halo, "flare_subflare_count", text="Subflares")
+            col.prop(halo, "flare_subflare_size", text="Subsize")
+
+    """
+
+
+classes = (
+    MaterialStrandSettings,
+)
+
+
+def register():
+    for cls in classes:
+        register_class(cls)
+
+    bpy.types.Material.strand = PointerProperty(type=MaterialStrandSettings)
+
+
+def unregister():
+    del bpy.types.Material.strand
+
+    for cls in reversed(classes):
+        unregister_class(cls)
diff --git a/render_povray/render.py b/render_povray/render.py
index 0f7da3c6b..150d892da 100755
--- a/render_povray/render.py
+++ b/render_povray/render.py
@@ -8,12 +8,11 @@ import bpy
 import subprocess
 import os
 from sys import platform
-import time
+#import time
 from math import (
     pi,
 )  # maybe move to scenography.py and topology_*****_data.py respectively with smoke and matrix
-
-import re
+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
@@ -21,19 +20,34 @@ 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 object_mesh_topology  # for mesh based geometry
-from . import object_curve_topology  # for curves based geometry
+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 object_primitives  # for import and export of POV specific primitives
+# 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 .object_primitives import write_object_modifiers
+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):
 
@@ -95,19 +109,31 @@ def renderable_objects():
     return [ob for ob in bpy.data.objects if is_renderable(ob)]
 
 
-def no_renderable_objects():
+def non_renderable_objects():
     """Boolean operands only. Not to render"""
     return list(csg_list)
 
 
-tab_level = 0
-unpacked_images = []
+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
 
-user_dir = bpy.utils.resource_path('USER')
-preview_dir = os.path.join(user_dir, "preview")
 
-# Make sure Preview directory exists and is empty
-smoke_path = os.path.join(preview_dir, "smoke.df3")
 
 '''
 
@@ -132,7 +158,7 @@ smoke_path = os.path.join(preview_dir, "smoke.df3")
 # # Maybe return that string to be added instead of directly written.
 
 # '''XXX WIP
-# import .object_mesh_topology.write_object_csg_inside_vector
+# import .model_all.write_object_csg_inside_vector
 # write_object_csg_inside_vector(ob, file)
 # '''
 
@@ -178,12 +204,55 @@ smoke_path = os.path.join(preview_dir, "smoke.df3")
 # 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 """
 
-    import mathutils
-
     with open(filename, "w") as file:
         # Only for testing
         if not scene:
@@ -191,12 +260,13 @@ def write_pov(filename, scene=None, info_callback=None):
 
         render = scene.render
         world = scene.world
-        global_matrix = mathutils.Matrix.Rotation(-pi / 2.0, 4, 'X')
+        global comments
         comments = scene.pov.comments_enable and not scene.pov.tempfiles_enable
-        linebreaksinlists = scene.pov.list_lf_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 = PovrayRender._locate_binary()
+        pov_binary = PovRender._locate_binary()
 
         if using_uberpov:
             print("Unofficial UberPOV feature set chosen in preferences")
@@ -207,40 +277,6 @@ def write_pov(filename, scene=None, info_callback=None):
         else:
             print("The name of the binary suggests you are probably rendering with standard POV engine")
 
-        def set_tab(tabtype, spaces):
-            tab_str = ""
-            if tabtype == 'NONE':
-                tab_str = ""
-            elif tabtype == 'TAB':
-                tab_str = "\t"
-            elif tabtype == 'SPACE':
-                tab_str = spaces * " "
-            return tab_str
-
-        tab = set_tab(scene.pov.indentation_character, scene.pov.indentation_spaces)
-        if not scene.pov.tempfiles_enable:
-
-            def tab_write(str_o):
-                """Indent POV syntax from brackets levels and write to exported file """
-                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
-
-        else:
-
-            def tab_write(str_o):
-                """write directly to exported file if user checked autonamed temp files (faster)."""
-
-                file.write(str_o)
 
         def unique_name(name, name_seq):
             """Increment any generated POV name that could get identical to avoid collisions"""
@@ -257,329 +293,20 @@ def write_pov(filename, scene=None, info_callback=None):
             name = string_strip_hyphen(name)
             return name
 
-        def write_matrix(matrix):
-            """Translate some transform matrix from Blender UI
-            to POV syntax and write to exported file """
-            tab_write(
-                "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],
-                )
-            )
-
         material_names_dictionary = {}
         DEF_MAT_NAME = ""  # or "Default"?
 
         # -----------------------------------------------------------------------------
 
-        def export_meta(metas):
-            """write all POV blob primitives and Blender Metas to exported file """
-            # TODO - blenders 'motherball' naming is not supported.
-
-            if comments and len(metas) >= 1:
-                file.write("//--Blob objects--\n\n")
-            # Get groups of metaballs by blender name prefix.
-            meta_group = {}
-            meta_elems = {}
-            for meta_ob in metas:
-                prefix = meta_ob.name.split(".")[0]
-                if prefix not in meta_group:
-                    meta_group[prefix] = meta_ob  # .data.threshold
-                elems = [
-                    (elem, meta_ob)
-                    for elem in meta_ob.data.elements
-                    if elem.type in {'BALL', 'ELLIPSOID', 'CAPSULE', 'CUBE', 'PLANE'}
-                ]
-                if prefix in meta_elems:
-                    meta_elems[prefix].extend(elems)
-                else:
-                    meta_elems[prefix] = elems
-
-                # empty metaball
-                if len(elems) == 0:
-                    tab_write("\n//dummy sphere to represent empty meta location\n")
-                    tab_write(
-                        "sphere {<%.6g, %.6g, %.6g>,0 pigment{rgbt 1} "
-                        "no_image no_reflection no_radiosity "
-                        "photons{pass_through collect off} hollow}\n\n"
-                        % (meta_ob.location.x, meta_ob.location.y, meta_ob.location.z)
-                    )  # meta_ob.name > povdataname)
-                # other metaballs
-                else:
-                    for mg, mob in meta_group.items():
-                        if len(meta_elems[mg]) != 0:
-                            tab_write("blob{threshold %.4g // %s \n" % (mob.data.threshold, mg))
-                            for elems in meta_elems[mg]:
-                                elem = elems[0]
-                                loc = elem.co
-                                stiffness = elem.stiffness
-                                if elem.use_negative:
-                                    stiffness = -stiffness
-                                if elem.type == 'BALL':
-                                    tab_write(
-                                        "sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g "
-                                        % (loc.x, loc.y, loc.z, elem.radius, stiffness)
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-                                elif elem.type == 'ELLIPSOID':
-                                    tab_write(
-                                        "sphere{ <%.6g, %.6g, %.6g>,%.4g,%.4g "
-                                        % (
-                                            loc.x / elem.size_x,
-                                            loc.y / elem.size_y,
-                                            loc.z / elem.size_z,
-                                            elem.radius,
-                                            stiffness,
-                                        )
-                                    )
-                                    tab_write(
-                                        "scale <%.6g, %.6g, %.6g>"
-                                        % (elem.size_x, elem.size_y, elem.size_z)
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-                                elif elem.type == 'CAPSULE':
-                                    tab_write(
-                                        "cylinder{ <%.6g, %.6g, %.6g>,<%.6g, %.6g, %.6g>,%.4g,%.4g "
-                                        % (
-                                            (loc.x - elem.size_x),
-                                            loc.y,
-                                            loc.z,
-                                            (loc.x + elem.size_x),
-                                            loc.y,
-                                            loc.z,
-                                            elem.radius,
-                                            stiffness,
-                                        )
-                                    )
-                                    # tab_write("scale <%.6g, %.6g, %.6g>" % (elem.size_x, elem.size_y, elem.size_z))
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-
-                                elif elem.type == 'CUBE':
-                                    tab_write(
-                                        "cylinder { -x*8, +x*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale  <1/4,1,1> scale <%.6g, %.6g, %.6g>\n"
-                                        % (
-                                            elem.radius * 2.0,
-                                            stiffness / 4.0,
-                                            loc.x,
-                                            loc.y,
-                                            loc.z,
-                                            elem.size_x,
-                                            elem.size_y,
-                                            elem.size_z,
-                                        )
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-                                    tab_write(
-                                        "cylinder { -y*8, +y*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1/4,1> scale <%.6g, %.6g, %.6g>\n"
-                                        % (
-                                            elem.radius * 2.0,
-                                            stiffness / 4.0,
-                                            loc.x,
-                                            loc.y,
-                                            loc.z,
-                                            elem.size_x,
-                                            elem.size_y,
-                                            elem.size_z,
-                                        )
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-                                    tab_write(
-                                        "cylinder { -z*8, +z*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1,1/4> scale <%.6g, %.6g, %.6g>\n"
-                                        % (
-                                            elem.radius * 2.0,
-                                            stiffness / 4.0,
-                                            loc.x,
-                                            loc.y,
-                                            loc.z,
-                                            elem.size_x,
-                                            elem.size_y,
-                                            elem.size_z,
-                                        )
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-
-                                elif elem.type == 'PLANE':
-                                    tab_write(
-                                        "cylinder { -x*8, +x*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale  <1/4,1,1> scale <%.6g, %.6g, %.6g>\n"
-                                        % (
-                                            elem.radius * 2.0,
-                                            stiffness / 4.0,
-                                            loc.x,
-                                            loc.y,
-                                            loc.z,
-                                            elem.size_x,
-                                            elem.size_y,
-                                            elem.size_z,
-                                        )
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-                                    tab_write(
-                                        "cylinder { -y*8, +y*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1/4,1> scale <%.6g, %.6g, %.6g>\n"
-                                        % (
-                                            elem.radius * 2.0,
-                                            stiffness / 4.0,
-                                            loc.x,
-                                            loc.y,
-                                            loc.z,
-                                            elem.size_x,
-                                            elem.size_y,
-                                            elem.size_z,
-                                        )
-                                    )
-                                    write_matrix(global_matrix @ elems[1].matrix_world)
-                                    tab_write("}\n")
-
-                            try:
-                                one_material = elems[1].data.materials[
-                                    0
-                                ]  # lame! - blender cant do enything else.
-                            except BaseException as e:
-                                print(e.__doc__)
-                                print('An exception occurred: {}'.format(e))
-                                one_material = None
-                            if one_material:
-                                diffuse_color = one_material.diffuse_color
-                                trans = 1.0 - one_material.pov.alpha
-                                if (
-                                    one_material.use_transparency
-                                    and one_material.transparency_method == 'RAYTRACE'
-                                ):
-                                    pov_filter = one_material.pov_raytrace_transparency.filter * (
-                                        1.0 - one_material.alpha
-                                    )
-                                    trans = (1.0 - one_material.pov.alpha) - pov_filter
-                                else:
-                                    pov_filter = 0.0
-                                material_finish = material_names_dictionary[one_material.name]
-                                tab_write(
-                                    "pigment {srgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} \n"
-                                    % (
-                                        diffuse_color[0],
-                                        diffuse_color[1],
-                                        diffuse_color[2],
-                                        pov_filter,
-                                        trans,
-                                    )
-                                )
-                                tab_write("finish{%s} " % safety(material_finish, ref_level_bound=2))
-                            else:
-                                material_finish = DEF_MAT_NAME
-                                trans = 0.0
-                                tab_write(
-                                    "pigment{srgbt<1,1,1,%.3g>} finish{%s} "
-                                    % (trans, safety(material_finish, ref_level_bound=2))
-                                )
-
-                                write_object_material_interior(one_material, mob, tab_write)
-                                # write_object_material_interior(one_material, elems[1])
-                                tab_write("radiosity{importance %3g}\n" % mob.pov.importance_value)
-                                tab_write("}\n\n")  # End of Metaball block
-
-        '''
-                meta = ob.data
-
-                # important because no elements will break parsing.
-                elements = [elem for elem in meta.elements if elem.type in {'BALL', 'ELLIPSOID'}]
-
-                if elements:
-                    tab_write("blob {\n")
-                    tab_write("threshold %.4g\n" % meta.threshold)
-                    importance = ob.pov.importance_value
-
-                    try:
-                        material = meta.materials[0]  # lame! - blender cant do enything else.
-                    except:
-                        material = None
-
-                    for elem in elements:
-                        loc = elem.co
-
-                        stiffness = elem.stiffness
-                        if elem.use_negative:
-                            stiffness = - stiffness
-
-                        if elem.type == 'BALL':
-
-                            tab_write("sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g }\n" %
-                                     (loc.x, loc.y, loc.z, elem.radius, stiffness))
-
-                            # After this wecould do something simple like...
-                            #     "pigment {Blue} }"
-                            # except we'll write the color
-
-                        elif elem.type == 'ELLIPSOID':
-                            # location is modified by scale
-                            tab_write("sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g }\n" %
-                                     (loc.x / elem.size_x,
-                                      loc.y / elem.size_y,
-                                      loc.z / elem.size_z,
-                                      elem.radius, stiffness))
-                            tab_write("scale <%.6g, %.6g, %.6g> \n" %
-                                     (elem.size_x, elem.size_y, elem.size_z))
-
-                    if material:
-                        diffuse_color = material.diffuse_color
-                        trans = 1.0 - material.pov.alpha
-                        if material.use_transparency and material.transparency_method == 'RAYTRACE':
-                            pov_filter = material.pov_raytrace_transparency.filter * (1.0 - material.alpha)
-                            trans = (1.0 - material.pov.alpha) - pov_filter
-                        else:
-                            pov_filter = 0.0
-
-                        material_finish = material_names_dictionary[material.name]
-
-                        tab_write("pigment {srgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} \n" %
-                                 (diffuse_color[0], diffuse_color[1], diffuse_color[2],
-                                  pov_filter, trans))
-                        tab_write("finish {%s}\n" % safety(material_finish, ref_level_bound=2))
-
-                    else:
-                        tab_write("pigment {srgb 1} \n")
-                        # Write the finish last.
-                        tab_write("finish {%s}\n" % (safety(DEF_MAT_NAME, ref_level_bound=2)))
-
-                    write_object_material_interior(material, elems[1])
-
-                    write_matrix(global_matrix @ ob.matrix_world)
-                    # Importance for radiosity sampling added here
-                    tab_write("radiosity { \n")
-                    # importance > ob.pov.importance_value
-                    tab_write("importance %3g \n" % importance)
-                    tab_write("}\n")
-
-                    tab_write("}\n")  # End of Metaball block
-
-                    if comments and len(metas) >= 1:
-                        file.write("\n")
-        '''
-
         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("global_settings {\n")
-            tab_write("assumed_gamma 1.0\n")
-            tab_write("max_trace_level %d\n" % scene.pov.max_trace_level)
+            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:
@@ -589,24 +316,24 @@ def write_pov(filename, scene=None, info_callback=None):
                 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("radiosity {\n")
-                tab_write("adc_bailout %.4g\n" % scene.pov.radio_adc_bailout)
-                tab_write("brightness %.4g\n" % scene.pov.radio_brightness)
-                tab_write("count %d\n" % scene.pov.radio_count)
-                tab_write("error_bound %.4g\n" % scene.pov.radio_error_bound)
-                tab_write("gray_threshold %.4g\n" % scene.pov.radio_gray_threshold)
-                tab_write("low_error_factor %.4g\n" % scene.pov.radio_low_error_factor)
-                tab_write("maximum_reuse %.4g\n" % scene.pov.radio_maximum_reuse)
-                tab_write("minimum_reuse %.4g\n" % scene.pov.radio_minimum_reuse)
-                tab_write("nearest_count %d\n" % scene.pov.radio_nearest_count)
-                tab_write("pretrace_start %.3g\n" % scene.pov.radio_pretrace_start)
-                tab_write("pretrace_end %.3g\n" % scene.pov.radio_pretrace_end)
-                tab_write("recursion_limit %d\n" % scene.pov.radio_recursion_limit)
-                tab_write("always_sample %d\n" % scene.pov.radio_always_sample)
-                tab_write("normal %d\n" % scene.pov.radio_normal)
-                tab_write("media %d\n" % scene.pov.radio_media)
-                tab_write("subsurface %d\n" % scene.pov.radio_subsurface)
-                tab_write("}\n")
+                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
@@ -614,7 +341,7 @@ def write_pov(filename, scene=None, info_callback=None):
                 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(
+                    tab_write(file,
                         "mm_per_unit %.6f\n" % (material.pov_subsurface_scattering.scale * 1000.0)
                     )
                     # 1000 rather than scale * (-100.0) + 15.0))
@@ -624,44 +351,48 @@ def write_pov(filename, scene=None, info_callback=None):
                     # formerly sslt_samples were multiplied by 100 instead of 10
                     sslt_samples = (11 - material.pov_subsurface_scattering.error_threshold) * 10
 
-                    tab_write("subsurface { samples %d, %d }\n" % (sslt_samples, sslt_samples / 10))
+                    tab_write(file, "subsurface { samples %d, %d }\n" % (sslt_samples, sslt_samples / 10))
                     once_sss = 0
 
                 if world and once_ambient:
-                    tab_write("ambient_light rgb<%.3g, %.3g, %.3g>\n" % world.pov.ambient_color[:])
+                    tab_write(file, "ambient_light rgb<%.3g, %.3g, %.3g>\n" % world.pov.ambient_color[:])
                     once_ambient = 0
 
-                if scene.pov.photon_enable:
-                    if once_photons and (
-                        material.pov.refraction_type == "2" or material.pov.photons_reflection
-                    ):
-                        tab_write("photons {\n")
-                        tab_write("spacing %.6f\n" % scene.pov.photon_spacing)
-                        tab_write("max_trace_level %d\n" % scene.pov.photon_max_trace_level)
-                        tab_write("adc_bailout %.3g\n" % scene.pov.photon_adc_bailout)
-                        tab_write(
-                            "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('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('load_file "%s"\n' % full_file_name)
-                        tab_write("}\n")
-                        once_photons = 0
-
-            tab_write("}\n")
+                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:
@@ -697,17 +428,17 @@ def write_pov(filename, scene=None, info_callback=None):
                     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(shading.export_pattern(texture))
+                    file.write(texturing_procedural.export_pattern(texture))
                 file.write("\n")
         if comments:
             file.write("\n//--Background--\n\n")
 
-        scenography.export_world(scene.world, scene, global_matrix, tab_write)
+        scenography.export_world(file, scene.world, scene, global_matrix, tab_write)
 
         if comments:
             file.write("\n//--Cameras--\n\n")
 
-        scenography.export_camera(scene, global_matrix, render, tab_write)
+        scenography.export_camera(file, scene, global_matrix, render, tab_write)
 
         if comments:
             file.write("\n//--Lamps--\n\n")
@@ -719,32 +450,15 @@ def write_pov(filename, scene=None, info_callback=None):
                         csg_list.append(mod.object)
         if csg_list:
             csg = False
-            sel = no_renderable_objects()
+            sel = non_renderable_objects()
             # export non rendered boolean objects operands
-            object_mesh_topology.export_meshes(
-                preview_dir,
+            model_all.objects_loop(
                 file,
                 scene,
                 sel,
                 csg,
-                string_strip_hyphen,
-                safety,
-                write_object_modifiers,
                 material_names_dictionary,
-                write_object_material_interior,
-                scenography.exported_lights_count,
                 unpacked_images,
-                image_format,
-                img_map,
-                img_map_transforms,
-                path_image,
-                smoke_path,
-                global_matrix,
-                write_matrix,
-                using_uberpov,
-                comments,
-                linebreaksinlists,
-                tab,
                 tab_level,
                 tab_write,
                 info_callback,
@@ -758,7 +472,6 @@ def write_pov(filename, scene=None, info_callback=None):
             file,
             scene,
             global_matrix,
-            write_matrix,
             tab_write,
         )
 
@@ -769,7 +482,6 @@ def write_pov(filename, scene=None, info_callback=None):
             file,
             scene,
             global_matrix,
-            write_matrix,
             tab_write,
         )
 
@@ -780,7 +492,7 @@ def write_pov(filename, scene=None, info_callback=None):
                 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'}):
-                object_curve_topology.export_curves(file, c, string_strip_hyphen, tab_write)
+                model_curve_topology.export_curves(file, c, tab_write)
 
         if comments:
             file.write("\n//--Material Definitions--\n\n")
@@ -789,10 +501,10 @@ def write_pov(filename, scene=None, info_callback=None):
         # Convert all materials to strings we can access directly per vertex.
         # exportMaterials()
         shading.write_material(
+            file,
             using_uberpov,
             DEF_MAT_NAME,
             tab_write,
-            safety,
             comments,
             unique_name,
             material_names_dictionary,
@@ -809,7 +521,7 @@ def write_pov(filename, scene=None, info_callback=None):
                     if len(ntree.nodes) == 0:
                         file.write('#declare %s = texture {%s}\n' % (pov_mat_name, pigment_color))
                     else:
-                        shading.write_nodes(pov_mat_name, ntree, file)
+                        nodes_fn.write_nodes(pov_mat_name, ntree, file)
 
                     for node in ntree.nodes:
                         if node:
@@ -829,10 +541,10 @@ def write_pov(filename, scene=None, info_callback=None):
                                     )
                 else:
                     shading.write_material(
+                        file,
                         using_uberpov,
                         DEF_MAT_NAME,
                         tab_write,
-                        safety,
                         comments,
                         unique_name,
                         material_names_dictionary,
@@ -842,46 +554,32 @@ def write_pov(filename, scene=None, info_callback=None):
         if comments:
             file.write("\n")
 
-        export_meta([m for m in sel if m.type == 'META'])
+        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()
-        object_mesh_topology.export_meshes(
-            preview_dir,
+        model_all.objects_loop(
             file,
             scene,
             sel,
             csg,
-            string_strip_hyphen,
-            safety,
-            write_object_modifiers,
             material_names_dictionary,
-            write_object_material_interior,
-            scenography.exported_lights_count,
             unpacked_images,
-            image_format,
-            img_map,
-            img_map_transforms,
-            path_image,
-            smoke_path,
-            global_matrix,
-            write_matrix,
-            using_uberpov,
-            comments,
-            linebreaksinlists,
-            tab,
             tab_level,
             tab_write,
             info_callback,
         )
         # totime = time.time() - tbefore
-        # print("export_meshes took" + str(totime))
+        # print("objects_loop took" + str(totime))
 
         # What follow used to happen here:
         # export_camera()
-        # scenography.export_world(scene.world, scene, global_matrix, tab_write)
+        # 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
@@ -893,6 +591,7 @@ def write_pov(filename, scene=None, info_callback=None):
 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
@@ -963,705 +662,6 @@ def write_pov_ini(filename_ini, filename_log, filename_pov, filename_image):
         file.close()
 
 
-class PovrayRender(bpy.types.RenderEngine):
-    """Define the external renderer"""
-
-    bl_idname = 'POVRAY_RENDER'
-    bl_label = "Persitence Of Vision"
-    bl_use_shading_nodes_custom = False
-    DELAY = 0.5
-
-    @staticmethod
-    def _locate_binary():
-        """Identify POV engine"""
-        addon_prefs = bpy.context.preferences.addons[__package__].preferences
-
-        # Use the system preference if its set.
-        pov_binary = addon_prefs.filepath_povray
-        if pov_binary:
-            if os.path.exists(pov_binary):
-                return pov_binary
-            # Implicit else, as here return was still not triggered:
-            print("User Preferences path to povray %r NOT FOUND, checking $PATH" % pov_binary)
-
-        # Windows Only
-        # assume if there is a 64bit binary that the user has a 64bit capable OS
-        if platform.startswith('win'):
-            import winreg
-
-            win_reg_key = winreg.OpenKey(
-                winreg.HKEY_CURRENT_USER, "Software\\POV-Ray\\v3.7\\Windows"
-            )
-            win_home = winreg.QueryValueEx(win_reg_key, "Home")[0]
-
-            # First try 64bits UberPOV
-            pov_binary = os.path.join(win_home, "bin", "uberpov64.exe")
-            if os.path.exists(pov_binary):
-                return pov_binary
-
-            # Then try 64bits POV
-            pov_binary = os.path.join(win_home, "bin", "pvengine64.exe")
-            if os.path.exists(pov_binary):
-                return pov_binary
-
-        # search the path all os's
-        pov_binary_default = "povray"
-
-        os_path_ls = os.getenv("PATH").split(':') + [""]
-
-        for dir_name in os_path_ls:
-            pov_binary = os.path.join(dir_name, pov_binary_default)
-            if os.path.exists(pov_binary):
-                return pov_binary
-        return ""
-
-    def _export(self, depsgraph, pov_path, image_render_path):
-        """gather all necessary output files paths user defined and auto generated and export there"""
-
-        scene = bpy.context.scene
-        if scene.pov.tempfiles_enable:
-            self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
-            # PNG with POV 3.7, can show the background color with alpha. In the long run using the
-            # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
-            self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
-            # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
-            self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
-            self._temp_file_log = os.path.join(tempfile.gettempdir(), "alltext.out")
-        else:
-            self._temp_file_in = pov_path + ".pov"
-            # PNG with POV 3.7, can show the background color with alpha. In the long run using the
-            # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
-            self._temp_file_out = image_render_path + ".png"
-            # self._temp_file_out = image_render_path + ".tga"
-            self._temp_file_ini = pov_path + ".ini"
-            log_path = bpy.path.abspath(scene.pov.scene_path).replace('\\', '/')
-            self._temp_file_log = os.path.join(log_path, "alltext.out")
-            '''
-            self._temp_file_in = "/test.pov"
-            # PNG with POV 3.7, can show the background color with alpha. In the long run using the
-            # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
-            self._temp_file_out = "/test.png"
-            #self._temp_file_out = "/test.tga"
-            self._temp_file_ini = "/test.ini"
-            '''
-        if scene.pov.text_block == "":
-
-            def info_callback(txt):
-                self.update_stats("", "POV-Ray 3.7: " + txt)
-
-            # os.makedirs(user_dir, exist_ok=True)  # handled with previews
-            os.makedirs(preview_dir, exist_ok=True)
-
-            write_pov(self._temp_file_in, scene, info_callback)
-        else:
-            pass
-
-    def _render(self, depsgraph):
-        """Export necessary files and render image."""
-        scene = bpy.context.scene
-        try:
-            os.remove(self._temp_file_out)  # so as not to load the old file
-        except OSError:
-            pass
-
-        pov_binary = PovrayRender._locate_binary()
-        if not pov_binary:
-            print("POV-Ray 3.7: could not execute povray, possibly POV-Ray isn't installed")
-            return False
-
-        write_pov_ini(
-            self._temp_file_ini, self._temp_file_log, self._temp_file_in, self._temp_file_out
-        )
-
-        print("***-STARTING-***")
-
-        extra_args = []
-
-        if scene.pov.command_line_switches != "":
-            for new_arg in scene.pov.command_line_switches.split(" "):
-                extra_args.append(new_arg)
-
-        self._is_windows = False
-        if platform.startswith('win'):
-            self._is_windows = True
-            if "/EXIT" not in extra_args and not scene.pov.pov_editor:
-                extra_args.append("/EXIT")
-        else:
-            # added -d option to prevent render window popup which leads to segfault on linux
-            extra_args.append("-d")
-
-        # Start Rendering!
-        try:
-            self._process = subprocess.Popen(
-                [pov_binary, self._temp_file_ini] + extra_args,
-                stdout=subprocess.PIPE,
-                stderr=subprocess.STDOUT,
-            )
-        except OSError:
-            # TODO, report api
-            print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
-            import traceback
-
-            traceback.print_exc()
-            print("***-DONE-***")
-            return False
-
-        else:
-            print("Engine ready!...")
-            print("Command line arguments passed: " + str(extra_args))
-            return True
-
-        # Now that we have a valid process
-
-    def _cleanup(self):
-        """Delete temp files and unpacked ones"""
-        for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
-            for i in range(5):
-                try:
-                    os.unlink(f)
-                    break
-                except OSError:
-                    # Wait a bit before retrying file might be still in use by Blender,
-                    # and Windows does not know how to delete a file in use!
-                    time.sleep(self.DELAY)
-        for i in unpacked_images:
-            for j in range(5):
-                try:
-                    os.unlink(i)
-                    break
-                except OSError:
-                    # Wait a bit before retrying file might be still in use by Blender,
-                    # and Windows does not know how to delete a file in use!
-                    time.sleep(self.DELAY)
-
-    def render(self, depsgraph):
-        """Export necessary files from text editor and render image."""
-
-        scene = bpy.context.scene
-        r = scene.render
-        x = int(r.resolution_x * r.resolution_percentage * 0.01)
-        y = int(r.resolution_y * r.resolution_percentage * 0.01)
-        print("***INITIALIZING***")
-
-        # This makes some tests on the render, returning True if all goes good, and False if
-        # it was finished one way or the other.
-        # It also pauses the script (time.sleep())
-        def _test_wait():
-            time.sleep(self.DELAY)
-
-            # User interrupts the rendering
-            if self.test_break():
-                try:
-                    self._process.terminate()
-                    print("***POV INTERRUPTED***")
-                except OSError:
-                    pass
-                return False
-            try:
-                poll_result = self._process.poll()
-            except AttributeError:
-                print("***CHECK POV PATH IN PREFERENCES***")
-                return False
-            # POV process is finisehd, one way or the other
-            if poll_result is not None:
-                if poll_result < 0:
-                    print("***POV PROCESS FAILED : %s ***" % poll_result)
-                    self.update_stats("", "POV-Ray 3.7: Failed")
-                return False
-
-            return True
-
-        if bpy.context.scene.pov.text_block != "":
-            if scene.pov.tempfiles_enable:
-                self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
-                self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
-                # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
-                self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
-                self._temp_file_log = os.path.join(tempfile.gettempdir(), "alltext.out")
-            else:
-                pov_path = scene.pov.text_block
-                image_render_path = os.path.splitext(pov_path)[0]
-                self._temp_file_out = os.path.join(preview_dir, image_render_path)
-                self._temp_file_in = os.path.join(preview_dir, pov_path)
-                self._temp_file_ini = os.path.join(
-                    preview_dir, (os.path.splitext(self._temp_file_in)[0] + ".INI")
-                )
-                self._temp_file_log = os.path.join(preview_dir, "alltext.out")
-
-            '''
-            try:
-                os.remove(self._temp_file_in)  # so as not to load the old file
-            except OSError:
-                pass
-            '''
-            print(scene.pov.text_block)
-            text = bpy.data.texts[scene.pov.text_block]
-            with open(self._temp_file_in, "w") as file:
-                # Why are the newlines needed?
-                file.write("\n")
-                file.write(text.as_string())
-                file.write("\n")
-            if not file.closed:
-                file.close()
-
-            # has to be called to update the frame on exporting animations
-            scene.frame_set(scene.frame_current)
-
-            pov_binary = PovrayRender._locate_binary()
-
-            if not pov_binary:
-                print("POV-Ray 3.7: could not execute povray, possibly POV-Ray isn't installed")
-                return False
-
-            # start ini UI options export
-            self.update_stats("", "POV-Ray 3.7: Exporting ini options from Blender")
-
-            write_pov_ini(
-                self._temp_file_ini,
-                self._temp_file_log,
-                self._temp_file_in,
-                self._temp_file_out,
-            )
-
-            print("***-STARTING-***")
-
-            extra_args = []
-
-            if scene.pov.command_line_switches != "":
-                for new_arg in scene.pov.command_line_switches.split(" "):
-                    extra_args.append(new_arg)
-
-            if platform.startswith('win'):
-                if "/EXIT" not in extra_args and not scene.pov.pov_editor:
-                    extra_args.append("/EXIT")
-            else:
-                # added -d option to prevent render window popup which leads to segfault on linux
-                extra_args.append("-d")
-
-            # Start Rendering!
-            try:
-                if scene.pov.sdl_window_enable and not platform.startswith(
-                    'win'
-                ):  # segfault on linux == False !!!
-                    env = {'POV_DISPLAY_SCALED': 'off'}
-                    env.update(os.environ)
-                    self._process = subprocess.Popen(
-                        [pov_binary, self._temp_file_ini],
-                        stdout=subprocess.PIPE,
-                        stderr=subprocess.STDOUT,
-                        env=env,
-                    )
-                else:
-                    self._process = subprocess.Popen(
-                        [pov_binary, self._temp_file_ini] + extra_args,
-                        stdout=subprocess.PIPE,
-                        stderr=subprocess.STDOUT,
-                    )
-            except OSError:
-                # TODO, report api
-                print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
-                import traceback
-
-                traceback.print_exc()
-                print("***-DONE-***")
-                return False
-
-            else:
-                print("Engine ready!...")
-                print("Command line arguments passed: " + str(extra_args))
-                # return True
-                self.update_stats("", "POV-Ray 3.7: Parsing File")
-
-            # Indented in main function now so repeated here but still not working
-            # to bring back render result to its buffer
-
-            if os.path.exists(self._temp_file_out):
-                xmin = int(r.border_min_x * x)
-                ymin = int(r.border_min_y * y)
-                xmax = int(r.border_max_x * x)
-                ymax = int(r.border_max_y * y)
-                result = self.begin_result(0, 0, x, y)
-                lay = result.layers[0]
-
-                time.sleep(self.DELAY)
-                try:
-                    lay.load_from_file(self._temp_file_out)
-                except RuntimeError:
-                    print("***POV ERROR WHILE READING OUTPUT FILE***")
-                self.end_result(result)
-            # print(self._temp_file_log) #bring the pov log to blender console with proper path?
-            with open(
-                self._temp_file_log
-            ) as f:  # The with keyword automatically closes the file when you are done
-                print(f.read())
-
-            self.update_stats("", "")
-
-            if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
-                self._cleanup()
-        else:
-
-            # WIP output format
-            #         if r.image_settings.file_format == 'OPENEXR':
-            #             fformat = 'EXR'
-            #             render.image_settings.color_mode = 'RGBA'
-            #         else:
-            #             fformat = 'TGA'
-            #             r.image_settings.file_format = 'TARGA'
-            #             r.image_settings.color_mode = 'RGBA'
-
-            blend_scene_name = bpy.data.filepath.split(os.path.sep)[-1].split(".")[0]
-            pov_scene_name = ""
-            pov_path = ""
-            image_render_path = ""
-
-            # has to be called to update the frame on exporting animations
-            scene.frame_set(scene.frame_current)
-
-            if not scene.pov.tempfiles_enable:
-
-                # check paths
-                pov_path = bpy.path.abspath(scene.pov.scene_path).replace('\\', '/')
-                if pov_path == "":
-                    if bpy.data.is_saved:
-                        pov_path = bpy.path.abspath("//")
-                    else:
-                        pov_path = tempfile.gettempdir()
-                elif pov_path.endswith("/"):
-                    if pov_path == "/":
-                        pov_path = bpy.path.abspath("//")
-                    else:
-                        pov_path = bpy.path.abspath(scene.pov.scene_path)
-
-                if not os.path.exists(pov_path):
-                    try:
-                        os.makedirs(pov_path)
-                    except BaseException as e:
-                        print(e.__doc__)
-                        print('An exception occurred: {}'.format(e))
-                        import traceback
-
-                        traceback.print_exc()
-
-                        print("POV-Ray 3.7: Cannot create scenes directory: %r" % pov_path)
-                        self.update_stats(
-                            "", "POV-Ray 3.7: Cannot create scenes directory %r" % pov_path
-                        )
-                        time.sleep(2.0)
-                        # return
-
-                '''
-                # Bug in POV-Ray RC3
-                image_render_path = bpy.path.abspath(scene.pov.renderimage_path).replace('\\','/')
-                if image_render_path == "":
-                    if bpy.data.is_saved:
-                        image_render_path = bpy.path.abspath("//")
-                    else:
-                        image_render_path = tempfile.gettempdir()
-                    #print("Path: " + image_render_path)
-                elif path.endswith("/"):
-                    if image_render_path == "/":
-                        image_render_path = bpy.path.abspath("//")
-                    else:
-                        image_render_path = bpy.path.abspath(scene.pov.)
-                if not os.path.exists(path):
-                    print("POV-Ray 3.7: Cannot find render image directory")
-                    self.update_stats("", "POV-Ray 3.7: Cannot find render image directory")
-                    time.sleep(2.0)
-                    return
-                '''
-
-                # check name
-                if scene.pov.scene_name == "":
-                    if blend_scene_name != "":
-                        pov_scene_name = blend_scene_name
-                    else:
-                        pov_scene_name = "untitled"
-                else:
-                    pov_scene_name = scene.pov.scene_name
-                    if os.path.isfile(pov_scene_name):
-                        pov_scene_name = os.path.basename(pov_scene_name)
-                    pov_scene_name = pov_scene_name.split('/')[-1].split('\\')[-1]
-                    if not pov_scene_name:
-                        print("POV-Ray 3.7: Invalid scene name")
-                        self.update_stats("", "POV-Ray 3.7: Invalid scene name")
-                        time.sleep(2.0)
-                        # return
-                    pov_scene_name = os.path.splitext(pov_scene_name)[0]
-
-                print("Scene name: " + pov_scene_name)
-                print("Export path: " + pov_path)
-                pov_path = os.path.join(pov_path, pov_scene_name)
-                pov_path = os.path.realpath(pov_path)
-
-                image_render_path = pov_path
-                # print("Render Image path: " + image_render_path)
-
-            # start export
-            self.update_stats("", "POV-Ray 3.7: Exporting data from Blender")
-            self._export(depsgraph, pov_path, image_render_path)
-            self.update_stats("", "POV-Ray 3.7: Parsing File")
-
-            if not self._render(depsgraph):
-                self.update_stats("", "POV-Ray 3.7: Not found")
-                # return
-
-            # r = scene.render
-            # compute resolution
-            # x = int(r.resolution_x * r.resolution_percentage * 0.01)
-            # y = int(r.resolution_y * r.resolution_percentage * 0.01)
-
-            # Wait for the file to be created
-            # XXX This is no more valid, as 3.7 always creates output file once render is finished!
-            parsing = re.compile(br"= \[Parsing\.\.\.\] =")
-            rendering = re.compile(br"= \[Rendering\.\.\.\] =")
-            percent = re.compile(r"\(([0-9]{1,3})%\)")
-            # print("***POV WAITING FOR FILE***")
-
-            data = b""
-            last_line = ""
-            while _test_wait():
-                # POV in Windows did not output its stdout/stderr, it displayed them in its GUI
-                # But now writes file
-                if self._is_windows:
-                    self.update_stats("", "POV-Ray 3.7: Rendering File")
-                else:
-                    t_data = self._process.stdout.read(10000)
-                    if not t_data:
-                        continue
-
-                    data += t_data
-                    # XXX This is working for UNIX, not sure whether it might need adjustments for
-                    #     other OSs
-                    # First replace is for windows
-                    t_data = str(t_data).replace('\\r\\n', '\\n').replace('\\r', '\r')
-                    lines = t_data.split('\\n')
-                    last_line += lines[0]
-                    lines[0] = last_line
-                    print('\n'.join(lines), end="")
-                    last_line = lines[-1]
-
-                    if rendering.search(data):
-                        _pov_rendering = True
-                        match = percent.findall(str(data))
-                        if match:
-                            self.update_stats("", "POV-Ray 3.7: Rendering File (%s%%)" % match[-1])
-                        else:
-                            self.update_stats("", "POV-Ray 3.7: Rendering File")
-
-                    elif parsing.search(data):
-                        self.update_stats("", "POV-Ray 3.7: Parsing File")
-
-            if os.path.exists(self._temp_file_out):
-                # print("***POV FILE OK***")
-                # self.update_stats("", "POV-Ray 3.7: Rendering")
-
-                # prev_size = -1
-
-                xmin = int(r.border_min_x * x)
-                ymin = int(r.border_min_y * y)
-                xmax = int(r.border_max_x * x)
-                ymax = int(r.border_max_y * y)
-
-                # print("***POV UPDATING IMAGE***")
-                result = self.begin_result(0, 0, x, y)
-                # XXX, tests for border render.
-                # result = self.begin_result(xmin, ymin, xmax - xmin, ymax - ymin)
-                # result = self.begin_result(0, 0, xmax - xmin, ymax - ymin)
-                lay = result.layers[0]
-
-                # This assumes the file has been fully written We wait a bit, just in case!
-                time.sleep(self.DELAY)
-                try:
-                    lay.load_from_file(self._temp_file_out)
-                    # XXX, tests for border render.
-                    # lay.load_from_file(self._temp_file_out, xmin, ymin)
-                except RuntimeError:
-                    print("***POV ERROR WHILE READING OUTPUT FILE***")
-
-                # Not needed right now, might only be useful if we find a way to use temp raw output of
-                # pov 3.7 (in which case it might go under _test_wait()).
-                '''
-                def update_image():
-                    # possible the image wont load early on.
-                    try:
-                        lay.load_from_file(self._temp_file_out)
-                        # XXX, tests for border render.
-                        #lay.load_from_file(self._temp_file_out, xmin, ymin)
-                        #lay.load_from_file(self._temp_file_out, xmin, ymin)
-                    except RuntimeError:
-                        pass
-
-                # Update while POV-Ray renders
-                while True:
-                    # print("***POV RENDER LOOP***")
-
-                    # test if POV-Ray exists
-                    if self._process.poll() is not None:
-                        print("***POV PROCESS FINISHED***")
-                        update_image()
-                        break
-
-                    # user exit
-                    if self.test_break():
-                        try:
-                            self._process.terminate()
-                            print("***POV PROCESS INTERRUPTED***")
-                        except OSError:
-                            pass
-
-                        break
-
-                    # Would be nice to redirect the output
-                    # stdout_value, stderr_value = self._process.communicate() # locks
-
-                    # check if the file updated
-                    new_size = os.path.getsize(self._temp_file_out)
-
-                    if new_size != prev_size:
-                        update_image()
-                        prev_size = new_size
-
-                    time.sleep(self.DELAY)
-                '''
-
-                self.end_result(result)
-
-            else:
-                print("***POV FILE NOT FOUND***")
-
-            print("***POV FILE FINISHED***")
-
-            # print(filename_log) #bring the pov log to blender console with proper path?
-            with open(
-                self._temp_file_log, encoding='utf-8'
-            ) as f:  # The with keyword automatically closes the file when you are done
-                msg = f.read()
-                # if isinstance(msg, str):
-                # stdmsg = msg
-                # decoded = False
-                # else:
-                # if type(msg) == bytes:
-                # stdmsg = msg.split('\n')
-                # stdmsg = msg.encode('utf-8', "replace")
-                # stdmsg = msg.encode("utf-8", "replace")
-
-                # stdmsg = msg.decode(encoding)
-                # decoded = True
-                # msg.encode('utf-8').decode('utf-8')
-                msg.replace("\t", "    ")
-                print(msg)
-                # Also print to the interactive console used in POV centric workspace
-                # To do: get a grip on new line encoding
-                # and make this a function to be used elsewhere
-                for win in bpy.context.window_manager.windows:
-                    if win.screen is not None:
-                        scr = win.screen
-                        for area in scr.areas:
-                            if area.type == 'CONSOLE':
-                                try:
-                                    # context override
-                                    ctx = {
-                                        'area': area,
-                                        'screen': scr,
-                                        'window': win
-                                    }
-
-                                    # bpy.ops.console.banner(ctx, text = "Hello world")
-                                    bpy.ops.console.clear_line(ctx)
-                                    for i in msg.split('\n'):
-                                        bpy.ops.console.scrollback_append(
-                                            ctx,
-                                            text=i,
-                                            type='INFO'
-                                        )
-                                        # bpy.ops.console.insert(ctx, text=(i + "\n"))
-                                except BaseException as e:
-                                    print(e.__doc__)
-                                    print('An exception occurred: {}'.format(e))
-                                    pass
-
-            self.update_stats("", "")
-
-            if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
-                self._cleanup()
-
-            sound_on = bpy.context.preferences.addons[__package__].preferences.use_sounds
-            finished_render_message = "\'Et VoilĂ !\'"
-
-            if platform.startswith('win') and sound_on:
-                # Could not find tts Windows command so playing beeps instead :-)
-                # "Korobeiniki"(Коробе́йники)
-                # aka "A-Type" Tetris theme
-                import winsound
-
-                winsound.Beep(494, 250)  # B
-                winsound.Beep(370, 125)  # F
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(440, 250)  # A
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(370, 125)  # F#
-                winsound.Beep(330, 275)  # E
-                winsound.Beep(330, 125)  # E
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(494, 275)  # B
-                winsound.Beep(440, 125)  # A
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(370, 275)  # F
-                winsound.Beep(370, 125)  # F
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(440, 250)  # A
-                winsound.Beep(494, 250)  # B
-                winsound.Beep(392, 250)  # G
-                winsound.Beep(330, 350)  # E
-                time.sleep(0.5)
-                winsound.Beep(440, 250)  # A
-                winsound.Beep(440, 150)  # A
-                winsound.Beep(523, 125)  # D8
-                winsound.Beep(659, 250)  # E8
-                winsound.Beep(587, 125)  # D8
-                winsound.Beep(523, 125)  # C8
-                winsound.Beep(494, 250)  # B
-                winsound.Beep(494, 125)  # B
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(494, 250)  # B
-                winsound.Beep(440, 150)  # A
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(370, 250)  # F#
-                winsound.Beep(370, 125)  # F#
-                winsound.Beep(392, 125)  # G
-                winsound.Beep(440, 250)  # A
-                winsound.Beep(494, 250)  # B
-                winsound.Beep(392, 250)  # G
-                winsound.Beep(330, 300)  # E
-
-            # Mac supports natively say command
-            elif platform == "darwin":
-                # We don't want the say command to block Python,
-                # so we add an ampersand after the message
-                # but if the os TTS package isn't up to date it
-                # still does thus, the try except clause
-                try:
-                    os.system("say %s &" % finished_render_message)
-                except BaseException as e:
-                    print(e.__doc__)
-                    print("your Mac may need an update, try to restart computer")
-                    pass
-            # While Linux frequently has espeak installed or at least can suggest
-            # Maybe windows could as well ?
-            elif platform == "linux":
-                # We don't want the espeak command to block Python,
-                # so we add an ampersand after the message
-                # but if the espeak TTS package isn't installed it
-                # still does thus, the try except clause
-                try:
-                    os.system("echo %s | espeak &" % finished_render_message)
-                except BaseException as e:
-                    print(e.__doc__)
-                    pass
-
-
-
 # --------------------------------------------------------------------------------- #
 # ----------------------------------- Operators ----------------------------------- #
 # --------------------------------------------------------------------------------- #
@@ -1703,7 +703,7 @@ class RenderPovTexturePreview(Operator):
         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(shading.export_pattern(tex))
+            file_pov.write(texturing_procedural.export_pattern(tex))
 
             file_pov.write("#declare Plane =\n")
             file_pov.write("mesh {\n")
@@ -1735,21 +735,22 @@ class RenderPovTexturePreview(Operator):
             file_pov.close()
         # ------------------------------- end write ------------------------------- #
 
-        pov_binary = PovrayRender._locate_binary()
+        pov_binary = PovRender._locate_binary()
 
         if platform.startswith('win'):
-            p1 = subprocess.Popen(
+            with subprocess.Popen(
                 ["%s" % pov_binary, "/EXIT", "%s" % ini_prev_file],
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT,
-            )
+            ) as p1:
+                p1.wait()
         else:
-            p1 = subprocess.Popen(
+            with subprocess.Popen(
                 ["%s" % pov_binary, "-d", "%s" % ini_prev_file],
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT,
-            )
-        p1.wait()
+            ) as p1:
+                p1.wait()
 
         tex.use_nodes = True
         tree = tex.node_tree
@@ -1793,7 +794,7 @@ class RunPovTextRender(Operator):
 
 
 classes = (
-    PovrayRender,
+    #PovRender,
     RenderPovTexturePreview,
     RunPovTextRender,
 )
diff --git a/render_povray/render_core.py b/render_povray/render_core.py
new file mode 100644
index 000000000..77714c6f0
--- /dev/null
+++ b/render_povray/render_core.py
@@ -0,0 +1,784 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""Define the POV render engine from generic Blender RenderEngine class."""
+import faulthandler
+faulthandler.enable()
+import bpy
+
+import builtins as __builtin__
+import subprocess
+import os
+from sys import platform
+import time
+import re
+import tempfile
+from bpy.utils import register_class, unregister_class
+from . import render
+
+def console_get(context):
+    #context = bpy.context
+    for win in context.window_manager.windows:
+        if win.screen is not None:
+            scr = win.screen
+            for area in scr.areas:
+                if area.type == 'CONSOLE':
+                    for space in area.spaces:
+                        if space.type == 'CONSOLE':
+                            return area, space, win, scr
+    return None, None, None, None
+
+def console_write(context, txt):
+    area, space, window, screen = console_get()
+    if space is None:
+        return
+    #context = bpy.context.copy()
+    context.update(dict(
+        area=area,
+        space_data=space,
+        region=area.regions[-1],
+        window=window,
+        screen=screen,
+    ))
+    for line in txt.split("\n"):
+        bpy.ops.console.scrollback_append(context, text=line, type='INFO')
+"""
+class RENDER_OT_test(bpy.types.Operator):
+    bl_idname = 'pov.oha_test'
+    bl_label = 'Test'
+    bl_options = {'REGISTER', 'UNDO'}
+
+    txt: bpy.props.StringProperty(
+        name='text',
+        default='what?'
+    )
+    def execute(self, context):
+        try:
+            console_write(context, self.txt)
+            return {'FINISHED'}
+        except:
+            self.report({'INFO'}, 'Printing report to Info window.')
+            return {'CANCELLED'}
+
+def console_print(*args, **kwargs):
+    context = bpy.context
+    #screens = (win.screen for win in context.window_manager.windows if win.screen is not None)
+    for win in context.window_manager.windows:
+        if win.screen is not None:
+            scr = win.screen
+            for a in scr.areas:
+                if a.type == 'CONSOLE':
+                    try:
+                        c = {}
+                        c['area'] = a
+                        c['space_data'] = a.spaces.active
+                        c['region'] = a.regions[-1]
+                        c['window'] = win
+                        c['screen'] = scr
+                        s = " ".join([str(arg) for arg in args])
+                        for line in s.split("\n"):
+                            bpy.ops.console.scrollback_append(c, text=line, type='INFO')
+
+                    except BaseException as e:
+                        print(e.__doc__)
+                        print('An exception occurred: {}'.format(e))
+                        pass
+
+
+def print(*args, **kwargs):
+    console_print(*args, **kwargs)       # to Python Console
+    __builtin__.print(*args, **kwargs)   # to System Console
+"""
+
+user_dir = bpy.utils.resource_path('USER')
+preview_dir = os.path.join(user_dir, "preview")
+# Make sure Preview directory exists and is empty
+smoke_path = os.path.join(preview_dir, "smoke.df3")
+
+class PovRender(bpy.types.RenderEngine):
+    """Define the external renderer"""
+
+    bl_idname = 'POVRAY_RENDER'
+    bl_label = "Persitence Of Vision"
+    bl_use_eevee_viewport = True
+    bl_use_shading_nodes_custom = False
+    DELAY = 0.5
+
+    @staticmethod
+    def _locate_binary():
+        """Identify POV engine"""
+        addon_prefs = bpy.context.preferences.addons[__package__].preferences
+
+        # Use the system preference if its set.
+        if pov_binary:= addon_prefs.filepath_povray:
+            if os.path.exists(pov_binary):
+                return pov_binary
+            # Implicit else, as here return was still not triggered:
+            print("User Preferences path to povray %r NOT FOUND, checking $PATH" % pov_binary)
+
+        # Windows Only
+        # assume if there is a 64bit binary that the user has a 64bit capable OS
+        if platform.startswith('win'):
+            import winreg
+
+            win_reg_key = winreg.OpenKey(
+                winreg.HKEY_CURRENT_USER, "Software\\POV-Ray\\v3.7\\Windows"
+            )
+            win_home = winreg.QueryValueEx(win_reg_key, "Home")[0]
+
+            # First try 64bits UberPOV
+            pov_binary = os.path.join(win_home, "bin", "uberpov64.exe")
+            if os.path.exists(pov_binary):
+                return pov_binary
+
+            # Then try 64bits POV
+            pov_binary = os.path.join(win_home, "bin", "pvengine64.exe")
+            if os.path.exists(pov_binary):
+                return pov_binary
+
+        # search the path all os's
+        pov_binary_default = "povray"
+
+        os_path_ls = os.getenv("PATH").split(':') + [""]
+
+        for dir_name in os_path_ls:
+            pov_binary = os.path.join(dir_name, pov_binary_default)
+            if os.path.exists(pov_binary):
+                return pov_binary
+        return ""
+
+    def _export(self, depsgraph, pov_path, image_render_path):
+        """gather all necessary output files paths user defined and auto generated and export there"""
+
+        scene = bpy.context.scene
+        if scene.pov.tempfiles_enable:
+            self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
+            # PNG with POV 3.7, can show the background color with alpha. In the long run using the
+            # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
+            self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
+            # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
+            self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
+            log_path = os.path.join(tempfile.gettempdir(), "alltext.out")
+        else:
+            self._temp_file_in = pov_path + ".pov"
+            # PNG with POV 3.7, can show the background color with alpha. In the long run using the
+            # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
+            self._temp_file_out = image_render_path + ".png"
+            # self._temp_file_out = image_render_path + ".tga"
+            self._temp_file_ini = pov_path + ".ini"
+            scene_path = scene.pov.scene_path
+            abs_log_path = bpy.path.abspath(scene_path)
+            log_path= os.path.join(abs_log_path, "alltext.out")
+            '''
+            self._temp_file_in = "/test.pov"
+            # PNG with POV 3.7, can show the background color with alpha. In the long run using the
+            # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
+            self._temp_file_out = "/test.png"
+            #self._temp_file_out = "/test.tga"
+            self._temp_file_ini = "/test.ini"
+            '''
+
+        self._temp_file_log = log_path
+        # self._temp_file_log = log_path.replace('\\', '/') # unnecessary relying on os.path
+
+        if scene.pov.text_block == "":
+
+            def info_callback(txt):
+                self.update_stats("", "POV-Ray 3.7: " + txt)
+
+            # os.makedirs(user_dir, exist_ok=True)  # handled with previews
+            os.makedirs(preview_dir, exist_ok=True)
+
+            render.write_pov(self._temp_file_in, scene, info_callback)
+        else:
+            pass
+
+    def _render(self, depsgraph):
+        """Export necessary files and render image."""
+        scene = bpy.context.scene
+        try:
+            os.remove(self._temp_file_out)  # so as not to load the old file
+        except OSError:
+            pass
+
+        pov_binary = PovRender._locate_binary()
+        if not pov_binary:
+            print("POV-Ray 3.7: could not execute povray, possibly POV-Ray isn't installed")
+            return False
+
+        render.write_pov_ini(
+            self._temp_file_ini, self._temp_file_log, self._temp_file_in, self._temp_file_out
+        )
+
+        print("***-STARTING-***")
+
+        extra_args = []
+        # Always add user preferences include path field when specified
+        if (pov_documents := bpy.context.preferences.addons[__package__].preferences.docpath_povray)!="":
+            extra_args.append("+L"+ pov_documents)
+        if scene.pov.command_line_switches != "":
+            extra_args.extend(iter(scene.pov.command_line_switches.split(" ")))
+        self._is_windows = False
+        if platform.startswith('win'):
+            self._is_windows = True
+            if "/EXIT" not in extra_args and not scene.pov.pov_editor:
+                extra_args.append("/EXIT")
+        else:
+            # added -d option to prevent render window popup which leads to segfault on linux
+            extra_args.append("-d")
+
+        # Start Rendering!
+        try:
+            self._process = subprocess.Popen(
+                [pov_binary, self._temp_file_ini] + extra_args,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT,
+            )
+        except OSError:
+            # TODO, report api
+            print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
+            import traceback
+
+            traceback.print_exc()
+            print("***-DONE-***")
+            return False
+
+        else:
+            print("Engine ready!...")
+            print("Command line arguments passed: " + str(extra_args))
+            return True
+
+    def _cleanup(self):
+        """Delete temp files and unpacked ones"""
+        for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
+            for i in range(5):
+                try:
+                    os.unlink(f)
+                    break
+                except OSError:
+                    # Wait a bit before retrying file might be still in use by Blender,
+                    # and Windows does not know how to delete a file in use!
+                    time.sleep(self.DELAY)
+        for i in render.unpacked_images:
+            for j in range(5):
+                try:
+                    os.unlink(i)
+                    break
+                except OSError:
+                    # Wait a bit before retrying file might be still in use by Blender,
+                    # and Windows does not know how to delete a file in use!
+                    time.sleep(self.DELAY)
+        # avoid some crashes if memory leaks from one render to the next?
+        #self.free_blender_memory()
+
+    def render(self, depsgraph):
+        """Export necessary files from text editor and render image."""
+
+        scene = bpy.context.scene
+        r = scene.render
+        x = int(r.resolution_x * r.resolution_percentage * 0.01)
+        y = int(r.resolution_y * r.resolution_percentage * 0.01)
+        print("\n***INITIALIZING***")
+
+        # This makes some tests on the render, returning True if all goes good, and False if
+        # it was finished one way or the other.
+        # It also pauses the script (time.sleep())
+        def _test_wait():
+            time.sleep(self.DELAY)
+
+            # User interrupts the rendering
+            if self.test_break():
+                try:
+                    self._process.terminate()
+                    print("***POV INTERRUPTED***")
+                except OSError:
+                    pass
+                return False
+            try:
+                poll_result = self._process.poll()
+            except AttributeError:
+                print("***CHECK POV PATH IN PREFERENCES***")
+                return False
+            # POV process is finisehd, one way or the other
+            if poll_result is not None:
+                if poll_result < 0:
+                    print("***POV PROCESS FAILED : %s ***" % poll_result)
+                    self.update_stats("", "POV-Ray 3.7: Failed")
+                return False
+
+            return True
+
+        if bpy.context.scene.pov.text_block != "":
+            if scene.pov.tempfiles_enable:
+                self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
+                self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
+                # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
+                self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
+                self._temp_file_log = os.path.join(tempfile.gettempdir(), "alltext.out")
+            else:
+                pov_path = scene.pov.text_block
+                image_render_path = os.path.splitext(pov_path)[0]
+                self._temp_file_out = os.path.join(preview_dir, image_render_path)
+                self._temp_file_in = os.path.join(preview_dir, pov_path)
+                self._temp_file_ini = os.path.join(
+                    preview_dir, (os.path.splitext(self._temp_file_in)[0] + ".INI")
+                )
+                self._temp_file_log = os.path.join(preview_dir, "alltext.out")
+
+            '''
+            try:
+                os.remove(self._temp_file_in)  # so as not to load the old file
+            except OSError:
+                pass
+            '''
+            print(scene.pov.text_block)
+            text = bpy.data.texts[scene.pov.text_block]
+            with open(self._temp_file_in, "w") as file:
+                # Why are the newlines needed?
+                file.write("\n")
+                file.write(text.as_string())
+                file.write("\n")
+
+
+            # has to be called to update the frame on exporting animations
+            scene.frame_set(scene.frame_current)
+
+            pov_binary = PovRender._locate_binary()
+
+            if not pov_binary:
+                print("Could not execute POV-Ray, which installation possibly isn't standard ?")
+                return False
+
+            # start ini UI options export
+            self.update_stats("", "POV-Ray 3.7: Exporting ini options from Blender")
+
+            render.write_pov_ini(
+                self._temp_file_ini,
+                self._temp_file_log,
+                self._temp_file_in,
+                self._temp_file_out,
+            )
+
+            print("***-STARTING-***")
+
+            extra_args = []
+
+            if scene.pov.command_line_switches != "":
+                for new_arg in scene.pov.command_line_switches.split(" "):
+                    extra_args.append(new_arg)
+
+            if platform.startswith('win'):
+                if "/EXIT" not in extra_args and not scene.pov.pov_editor:
+                    extra_args.append("/EXIT")
+            else:
+                # added -d option to prevent render window popup which leads to segfault on linux
+                extra_args.append("-d")
+
+            # Start Rendering!
+            try:
+                if scene.pov.sdl_window_enable and not platform.startswith(
+                    'win'
+                ):  # segfault on linux == False !!!
+                    env = {'POV_DISPLAY_SCALED': 'off'}
+                    env.update(os.environ)
+                    self._process = subprocess.Popen(
+                        [pov_binary, self._temp_file_ini],
+                        stdout=subprocess.PIPE,
+                        stderr=subprocess.STDOUT,
+                        env=env,
+                    )
+                else:
+                    self._process = subprocess.Popen(
+                        [pov_binary, self._temp_file_ini] + extra_args,
+                        stdout=subprocess.PIPE,
+                        stderr=subprocess.STDOUT,
+                    )
+            except OSError:
+                # TODO, report api
+                print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
+                import traceback
+
+                traceback.print_exc()
+                print("***-DONE-***")
+                return False
+
+            else:
+                print("Engine ready!...")
+                print("Command line arguments passed: " + str(extra_args))
+                # return True
+                self.update_stats("", "POV-Ray 3.7: Parsing File")
+
+            # Indented in main function now so repeated here but still not working
+            # to bring back render result to its buffer
+
+            if os.path.exists(self._temp_file_out):
+                xmin = int(r.border_min_x * x)
+                ymin = int(r.border_min_y * y)
+                xmax = int(r.border_max_x * x)
+                ymax = int(r.border_max_y * y)
+                result = self.begin_result(0, 0, x, y)
+                lay = result.layers[0]
+
+                time.sleep(self.DELAY)
+                try:
+                    lay.load_from_file(self._temp_file_out)
+                except RuntimeError:
+                    print("***POV ERROR WHILE READING OUTPUT FILE***")
+                self.end_result(result)
+            # print(self._temp_file_log) #bring the pov log to blender console with proper path?
+            with open(
+                self._temp_file_log
+            ) as f:  # The with keyword automatically closes the file when you are done
+                print(f.read()) # console_write(f.read())
+
+            self.update_stats("", "")
+
+            if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
+                self._cleanup()
+        else:
+
+            # WIP output format
+            #         if r.image_settings.file_format == 'OPENEXR':
+            #             fformat = 'EXR'
+            #             render.image_settings.color_mode = 'RGBA'
+            #         else:
+            #             fformat = 'TGA'
+            #             r.image_settings.file_format = 'TARGA'
+            #             r.image_settings.color_mode = 'RGBA'
+
+            blend_scene_name = bpy.data.filepath.split(os.path.sep)[-1].split(".")[0]
+            pov_scene_name = ""
+            pov_path = ""
+            image_render_path = ""
+
+            # has to be called to update the frame on exporting animations
+            scene.frame_set(scene.frame_current)
+
+            if not scene.pov.tempfiles_enable:
+
+                # check paths
+                pov_path = bpy.path.abspath(scene.pov.scene_path).replace('\\', '/')
+                if pov_path == "":
+                    if bpy.data.is_saved:
+                        pov_path = bpy.path.abspath("//")
+                    else:
+                        pov_path = tempfile.gettempdir()
+                elif pov_path.endswith("/"):
+                    if pov_path == "/":
+                        pov_path = bpy.path.abspath("//")
+                    else:
+                        pov_path = bpy.path.abspath(scene.pov.scene_path)
+
+                if not os.path.exists(pov_path):
+                    try:
+                        os.makedirs(pov_path)
+                    except BaseException as e:
+                        print(e.__doc__)
+                        print('An exception occurred: {}'.format(e))
+                        import traceback
+
+                        traceback.print_exc()
+
+                        print("POV-Ray 3.7: Cannot create scenes directory: %r" % pov_path)
+                        self.update_stats(
+                            "", "POV-Ray 3.7: Cannot create scenes directory %r" % pov_path
+                        )
+                        time.sleep(2.0)
+                        # return
+
+                '''
+                # Bug in POV-Ray RC3
+                image_render_path = bpy.path.abspath(scene.pov.renderimage_path).replace('\\','/')
+                if image_render_path == "":
+                    if bpy.data.is_saved:
+                        image_render_path = bpy.path.abspath("//")
+                    else:
+                        image_render_path = tempfile.gettempdir()
+                    #print("Path: " + image_render_path)
+                elif path.endswith("/"):
+                    if image_render_path == "/":
+                        image_render_path = bpy.path.abspath("//")
+                    else:
+                        image_render_path = bpy.path.abspath(scene.pov.)
+                if not os.path.exists(path):
+                    print("POV-Ray 3.7: Cannot find render image directory")
+                    self.update_stats("", "POV-Ray 3.7: Cannot find render image directory")
+                    time.sleep(2.0)
+                    return
+                '''
+
+                # check name
+                if scene.pov.scene_name == "":
+                    if blend_scene_name != "":
+                        pov_scene_name = blend_scene_name
+                    else:
+                        pov_scene_name = "untitled"
+                else:
+                    pov_scene_name = scene.pov.scene_name
+                    if os.path.isfile(pov_scene_name):
+                        pov_scene_name = os.path.basename(pov_scene_name)
+                    pov_scene_name = pov_scene_name.split('/')[-1].split('\\')[-1]
+                    if not pov_scene_name:
+                        print("POV-Ray 3.7: Invalid scene name")
+                        self.update_stats("", "POV-Ray 3.7: Invalid scene name")
+                        time.sleep(2.0)
+                        # return
+                    pov_scene_name = os.path.splitext(pov_scene_name)[0]
+
+                print("Scene name: " + pov_scene_name)
+                print("Export path: " + pov_path)
+                pov_path = os.path.join(pov_path, pov_scene_name)
+                pov_path = os.path.realpath(pov_path)
+
+                image_render_path = pov_path
+                # print("Render Image path: " + image_render_path)
+
+            # start export
+            self.update_stats("", "POV-Ray 3.7: Exporting data from Blender")
+            self._export(depsgraph, pov_path, image_render_path)
+            self.update_stats("", "POV-Ray 3.7: Parsing File")
+
+            if not self._render(depsgraph):
+                self.update_stats("", "POV-Ray 3.7: Not found")
+                # return
+
+            # r = scene.render
+            # compute resolution
+            # x = int(r.resolution_x * r.resolution_percentage * 0.01)
+            # y = int(r.resolution_y * r.resolution_percentage * 0.01)
+
+            # Wait for the file to be created
+            # XXX This is no more valid, as 3.7 always creates output file once render is finished!
+            parsing = re.compile(br"= \[Parsing\.\.\.\] =")
+            rendering = re.compile(br"= \[Rendering\.\.\.\] =")
+            percent = re.compile(r"\(([0-9]{1,3})%\)")
+            # print("***POV WAITING FOR FILE***")
+
+            data = b""
+            last_line = ""
+            while _test_wait():
+                # POV in Windows did not output its stdout/stderr, it displayed them in its GUI
+                # But now writes file
+                if self._is_windows:
+                    self.update_stats("", "POV-Ray 3.7: Rendering File")
+                else:
+                    t_data = self._process.stdout.read(10000)
+                    if not t_data:
+                        continue
+
+                    data += t_data
+                    # XXX This is working for UNIX, not sure whether it might need adjustments for
+                    #     other OSs
+                    # First replace is for windows
+                    t_data = str(t_data).replace('\\r\\n', '\\n').replace('\\r', '\r')
+                    lines = t_data.split('\\n')
+                    last_line += lines[0]
+                    lines[0] = last_line
+                    print('\n'.join(lines), end="")
+                    last_line = lines[-1]
+
+                    if rendering.search(data):
+                        _pov_rendering = True
+                        match = percent.findall(str(data))
+                        if match:
+                            self.update_stats("", "POV-Ray 3.7: Rendering File (%s%%)" % match[-1])
+                        else:
+                            self.update_stats("", "POV-Ray 3.7: Rendering File")
+
+                    elif parsing.search(data):
+                        self.update_stats("", "POV-Ray 3.7: Parsing File")
+
+            if os.path.exists(self._temp_file_out):
+                # print("***POV FILE OK***")
+                # self.update_stats("", "POV-Ray 3.7: Rendering")
+
+                # prev_size = -1
+
+                xmin = int(r.border_min_x * x)
+                ymin = int(r.border_min_y * y)
+                xmax = int(r.border_max_x * x)
+                ymax = int(r.border_max_y * y)
+
+                # print("***POV UPDATING IMAGE***")
+                result = self.begin_result(0, 0, x, y)
+                # XXX, tests for border render.
+                # result = self.begin_result(xmin, ymin, xmax - xmin, ymax - ymin)
+                # result = self.begin_result(0, 0, xmax - xmin, ymax - ymin)
+                lay = result.layers[0]
+
+                # This assumes the file has been fully written We wait a bit, just in case!
+                time.sleep(self.DELAY)
+                try:
+                    lay.load_from_file(self._temp_file_out)
+                    # XXX, tests for border render.
+                    # lay.load_from_file(self._temp_file_out, xmin, ymin)
+                except RuntimeError:
+                    print("***POV ERROR WHILE READING OUTPUT FILE***")
+
+                # Not needed right now, might only be useful if we find a way to use temp raw output of
+                # pov 3.7 (in which case it might go under _test_wait()).
+                '''
+                def update_image():
+                    # possible the image wont load early on.
+                    try:
+                        lay.load_from_file(self._temp_file_out)
+                        # XXX, tests for border render.
+                        #lay.load_from_file(self._temp_file_out, xmin, ymin)
+                        #lay.load_from_file(self._temp_file_out, xmin, ymin)
+                    except RuntimeError:
+                        pass
+
+                # Update while POV-Ray renders
+                while True:
+                    # print("***POV RENDER LOOP***")
+
+                    # test if POV-Ray exists
+                    if self._process.poll() is not None:
+                        print("***POV PROCESS FINISHED***")
+                        update_image()
+                        break
+
+                    # user exit
+                    if self.test_break():
+                        try:
+                            self._process.terminate()
+                            print("***POV PROCESS INTERRUPTED***")
+                        except OSError:
+                            pass
+
+                        break
+
+                    # Would be nice to redirect the output
+                    # stdout_value, stderr_value = self._process.communicate() # locks
+
+                    # check if the file updated
+                    new_size = os.path.getsize(self._temp_file_out)
+
+                    if new_size != prev_size:
+                        update_image()
+                        prev_size = new_size
+
+                    time.sleep(self.DELAY)
+                '''
+
+                self.end_result(result)
+            else:
+                print("***NO POV OUTPUT IMAGE***")
+
+            print("***POV INPUT FILE WRITTEN***")
+
+            # print(filename_log) #bring the pov log to blender console with proper path?
+            try:
+                with open(
+                    self._temp_file_log, encoding='utf-8'
+                ) as f:  # The with keyword automatically closes the file when you are done
+                    msg = f.read()
+                    if isinstance(msg, str):
+                        stdmsg = msg
+                        #decoded = False
+                    elif type(msg) == bytes:
+                        #stdmsg = msg.split('\n')
+                        stdmsg = msg.encode('utf-8', "replace")
+                    # stdmsg = msg.encode("utf-8", "replace")
+
+                    # stdmsg = msg.decode(encoding)
+                    # decoded = True
+                    # msg.encode('utf-8').decode('utf-8')
+                    stdmsg.replace("\t", "    ")
+                    print(stdmsg) # console_write(stdmsg) # todo fix segfault and use
+            except FileNotFoundError:
+                print("No render log to read")
+            self.update_stats("", "")
+
+            if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
+                self._cleanup()
+
+            sound_on = bpy.context.preferences.addons[__package__].preferences.use_sounds
+            finished_render_message = "\'Et VoilĂ !\'"
+
+            if platform.startswith('win') and sound_on:
+                # Could not find tts Windows command so playing beeps instead :-)
+                # "Korobeiniki"(Коробе́йники)
+                # aka "A-Type" Tetris theme
+                import winsound
+
+                winsound.Beep(494, 250)  # B
+                winsound.Beep(370, 125)  # F
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(440, 250)  # A
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(370, 125)  # F#
+                winsound.Beep(330, 275)  # E
+                winsound.Beep(330, 125)  # E
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(494, 275)  # B
+                winsound.Beep(440, 125)  # A
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(370, 275)  # F
+                winsound.Beep(370, 125)  # F
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(440, 250)  # A
+                winsound.Beep(494, 250)  # B
+                winsound.Beep(392, 250)  # G
+                winsound.Beep(330, 350)  # E
+                time.sleep(0.5)
+                winsound.Beep(440, 250)  # A
+                winsound.Beep(440, 150)  # A
+                winsound.Beep(523, 125)  # D8
+                winsound.Beep(659, 250)  # E8
+                winsound.Beep(587, 125)  # D8
+                winsound.Beep(523, 125)  # C8
+                winsound.Beep(494, 250)  # B
+                winsound.Beep(494, 125)  # B
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(494, 250)  # B
+                winsound.Beep(440, 150)  # A
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(370, 250)  # F#
+                winsound.Beep(370, 125)  # F#
+                winsound.Beep(392, 125)  # G
+                winsound.Beep(440, 250)  # A
+                winsound.Beep(494, 250)  # B
+                winsound.Beep(392, 250)  # G
+                winsound.Beep(330, 300)  # E
+
+            # Mac supports natively say command
+            elif platform == "darwin":
+                # We don't want the say command to block Python,
+                # so we add an ampersand after the message
+                # but if the os TTS package isn't up to date it
+                # still does thus, the try except clause
+                try:
+                    os.system("say -v Amelie %s &" % finished_render_message)
+                except BaseException as e:
+                    print(e.__doc__)
+                    print("your Mac may need an update, try to restart computer")
+                    pass
+            # While Linux frequently has espeak installed or at least can suggest
+            # Maybe windows could as well ?
+            elif platform == "linux":
+                # We don't want the espeak command to block Python,
+                # so we add an ampersand after the message
+                # but if the espeak TTS package isn't installed it
+                # still does thus, the try except clause
+                try:
+                    os.system("echo %s | espeak &" % finished_render_message)
+                except BaseException as e:
+                    print(e.__doc__)
+                    pass
+
+classes = (
+    PovRender,
+)
+
+
+def register():
+    for cls in classes:
+        register_class(cls)
+
+
+
+def unregister():
+    for cls in reversed(classes):
+        unregister_class(cls)
diff --git a/render_povray/render_gui.py b/render_povray/render_gui.py
index cc3dcf239..465f5a48b 100755
--- a/render_povray/render_gui.py
+++ b/render_povray/render_gui.py
@@ -21,7 +21,7 @@ from bl_ui import properties_output
 for member in dir(properties_output):
     subclass = getattr(properties_output, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_output
 
 from bl_ui import properties_freestyle
@@ -29,10 +29,9 @@ from bl_ui import properties_freestyle
 for member in dir(properties_freestyle):
     subclass = getattr(properties_freestyle, member)
     if hasattr(subclass, "COMPAT_ENGINES") and (
-        subclass.bl_space_type != 'PROPERTIES'
-        or subclass.bl_context != "render"
+        subclass.bl_space_type != "PROPERTIES" or subclass.bl_context != "render"
     ):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
         # subclass.bl_parent_id = "RENDER_PT_POV_filter"
 del properties_freestyle
 
@@ -41,7 +40,7 @@ from bl_ui import properties_view_layer
 for member in dir(properties_view_layer):
     subclass = getattr(properties_view_layer, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_view_layer
 
 # Use some of the existing buttons.
@@ -68,10 +67,10 @@ class RenderButtonsPanel:
     """Use this class to define buttons from the render tab of
     properties window."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "render"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -82,16 +81,16 @@ class RenderButtonsPanel:
 class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel):
     """Use this class to define pov ini settings buttons."""
 
-    bl_options = {'DEFAULT_CLOSED'}
+    bl_options = {"DEFAULT_CLOSED"}
     bl_label = "Auto Start"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         scene = context.scene
         if scene.pov.tempfiles_enable:
-            self.layout.prop(scene.pov, "tempfiles_enable", text="", icon='AUTO')
+            self.layout.prop(scene.pov, "tempfiles_enable", text="", icon="AUTO")
         else:
-            self.layout.prop(scene.pov, "tempfiles_enable", text="", icon='CONSOLE')
+            self.layout.prop(scene.pov, "tempfiles_enable", text="", icon="CONSOLE")
 
     def draw(self, context):
 
@@ -104,13 +103,15 @@ class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel):
 
         col = split.column()
         col.label(text="Command line options:")
-        col.prop(scene.pov, "command_line_switches", text="", icon='RIGHTARROW')
+        col.prop(scene.pov, "command_line_switches", text="", icon="RIGHTARROW")
         split = layout.split()
 
         # layout.active = not scene.pov.tempfiles_enable
         if not scene.pov.tempfiles_enable:
-            split.prop(scene.pov, "deletefiles_enable", text="Delete files")
-            split.prop(scene.pov, "pov_editor", text="POV Editor")
+            split.prop(scene.pov, "deletefiles_enable", text="Delete")
+            if not platform.startswith("win"):
+                split.prop(scene.pov, "sdl_window_enable", text="Show")
+            split.prop(scene.pov, "pov_editor", text="Edit")
 
             col = layout.column()
             col.prop(scene.pov, "scene_name", text="Name")
@@ -120,7 +121,7 @@ class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel):
 
             split = layout.split()
             split.prop(scene.pov, "indentation_character", text="Indent")
-            if scene.pov.indentation_character == 'SPACE':
+            if scene.pov.indentation_character == "SPACE":
                 split.prop(scene.pov, "indentation_spaces", text="Spaces")
 
             row = layout.row()
@@ -129,19 +130,35 @@ class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel):
 
 
 class RENDER_PT_POV_render_settings(RenderButtonsPanel, Panel):
-    """Use this class to define pov render settings buttons."""
+    """Use this class to host pov render settings buttons from other sub panels."""
 
     bl_label = "Global Settings"
-    bl_icon = 'SETTINGS'
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_icon = "SETTINGS"
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         scene = context.scene
         if scene.pov.global_settings_advanced:
-            self.layout.prop(scene.pov, "global_settings_advanced", text="", icon='SETTINGS')
+            self.layout.prop(scene.pov, "global_settings_advanced", text="", icon="PREFERENCES")
         else:
-            self.layout.prop(scene.pov, "global_settings_advanced", text="", icon='PREFERENCES')
+            self.layout.prop(scene.pov, "global_settings_advanced", text="", icon="SETTINGS")
+
+    def draw(self, context):
+        layout = self.layout
+
+        scene = context.scene
+
+        layout.active = scene.pov.global_settings_advanced
+
+
+class RENDER_PT_POV_light_paths(RenderButtonsPanel, Panel):
+    """Use this class to define pov's main light ray relative settings buttons."""
+
+    bl_label = "Light Paths Tracing"
+    bl_parent_id = "RENDER_PT_POV_render_settings"
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
@@ -149,41 +166,110 @@ class RENDER_PT_POV_render_settings(RenderButtonsPanel, Panel):
         scene = context.scene
         # rd = context.scene.render
         # layout.active = (scene.pov.max_trace_level != 0)
+        layout.active = scene.pov.global_settings_advanced
+        if scene.pov.use_shadows:
+            layout.prop(scene.pov, "use_shadows", icon="COMMUNITY")
+        else:
+            layout.prop(scene.pov, "use_shadows", icon="USER")
+        col = layout.column()
+        col.prop(scene.pov, "max_trace_level", text="Ray Depth")
+        row = layout.row(align=True)
+        row.prop(scene.pov, "adc_bailout")
+
+
+class RENDER_PT_POV_film(RenderButtonsPanel, Panel):
+    """Use this class to define pov film settings buttons."""
+
+    bl_label = "Film"
+    bl_parent_id = "RENDER_PT_POV_render_settings"
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
-        if not platform.startswith('win'):
-            layout.prop(scene.pov, "sdl_window_enable", text="POV-Ray SDL Window")
+    def draw(self, context):
+        layout = self.layout
+
+        povprops = context.scene.pov
+        agnosticprops = context.scene.render
 
+        layout.active = povprops.global_settings_advanced
         col = layout.column()
-        col.label(text="Main Path Tracing:")
-        col.prop(scene.pov, "max_trace_level", text="Ray Depth")
-        align = True
+        col.label(text="Background")
+        row = layout.row(align=True)
+        if agnosticprops.film_transparent:
+            row.prop(
+                agnosticprops,
+                "film_transparent",
+                text="Blender alpha",
+                icon="NODE_COMPOSITING",
+                invert_checkbox=True,
+            )
+        else:
+            row.prop(
+                agnosticprops,
+                "film_transparent",
+                text="POV alpha",
+                icon="IMAGE_ALPHA",
+                invert_checkbox=True,
+            )
+            row.prop(povprops, "alpha_mode", text="")
+            if povprops.alpha_mode == "SKY":
+                row.label(text=" (color only)")
+            elif povprops.alpha_mode == "TRANSPARENT":
+                row.prop(povprops, "alpha_filter", text="(premultiplied)", slider=True)
+            else:
+                # povprops.alpha_mode == 'STRAIGHT'
+                row.label(text=" (unassociated)")
+
+
+class RENDER_PT_POV_hues(RenderButtonsPanel, Panel):
+    """Use this class to define pov RGB tweaking buttons."""
+
+    bl_label = "Hues"
+    bl_parent_id = "RENDER_PT_POV_render_settings"
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    def draw(self, context):
+        layout = self.layout
+
+        scene = context.scene
+
         layout.active = scene.pov.global_settings_advanced
-        row = layout.row(align=align)
-        row.prop(scene.pov, "adc_bailout")
-        row = layout.row(align=align)
+
+        row = layout.row(align=True)
         row.prop(scene.pov, "ambient_light")
-        row = layout.row(align=align)
+        row = layout.row(align=True)
         row.prop(scene.pov, "irid_wavelength")
-        row = layout.row(align=align)
-        row.prop(scene.pov, "number_of_waves")
-        row = layout.row(align=align)
-        row.prop(scene.pov, "noise_generator")
+        row = layout.row(align=True)
 
-        split = layout.split()
-        split.label(text="Shading:")
-        split = layout.split()
 
-        row = split.row(align=align)
-        row.prop(scene.pov, "use_shadows")
-        row.prop(scene.pov, "alpha_mode")
+class RENDER_PT_POV_pattern_rules(RenderButtonsPanel, Panel):
+    """Use this class to change pov sets of texture generating algorythms."""
+
+    bl_label = "Pattern Rules"
+    bl_parent_id = "RENDER_PT_POV_render_settings"
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    def draw(self, context):
+        layout = self.layout
+
+        scene = context.scene
+
+        layout.active = scene.pov.global_settings_advanced
+
+        row = layout.row(align=True)
+        row.prop(scene.pov, "number_of_waves")
+        row = layout.row(align=True)
+        row.prop(scene.pov, "noise_generator")
 
 
 class RENDER_PT_POV_photons(RenderButtonsPanel, Panel):
     """Use this class to define pov photons buttons."""
 
     bl_label = "Photons"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     # def draw_header(self, context):
     # self.layout.label(icon='SETTINGS')
@@ -191,9 +277,9 @@ class RENDER_PT_POV_photons(RenderButtonsPanel, Panel):
     def draw_header(self, context):
         scene = context.scene
         if scene.pov.photon_enable:
-            self.layout.prop(scene.pov, "photon_enable", text="", icon='PMARKER_ACT')
+            self.layout.prop(scene.pov, "photon_enable", text="", icon="PARTICLES")
         else:
-            self.layout.prop(scene.pov, "photon_enable", text="", icon='PMARKER')
+            self.layout.prop(scene.pov, "photon_enable", text="", icon="MOD_PARTICLES")
 
     def draw(self, context):
         scene = context.scene
@@ -214,20 +300,20 @@ class RENDER_PT_POV_photons(RenderButtonsPanel, Panel):
         col.prop(scene.pov, "photon_gather_max")
 
         box = layout.box()
-        box.label(text='Photon Map File:')
+        box.label(text="Photon Map File:")
         row = box.row()
         row.prop(scene.pov, "photon_map_file_save_load", expand=True)
-        if scene.pov.photon_map_file_save_load in {'save'}:
+        if scene.pov.photon_map_file_save_load in {"save"}:
             box.prop(scene.pov, "photon_map_dir")
             box.prop(scene.pov, "photon_map_filename")
-        if scene.pov.photon_map_file_save_load in {'load'}:
+        if scene.pov.photon_map_file_save_load in {"load"}:
             box.prop(scene.pov, "photon_map_file")
         # end main photons
 
 
 def uberpov_only_qmc_til_pov38release(layout):
     col = layout.column()
-    col.alignment = 'CENTER'
+    col.alignment = "CENTER"
     col.label(text="Stochastic Anti Aliasing is")
     col.label(text="Only Available with UberPOV")
     col.label(text="Feature Set in User Preferences.")
@@ -252,18 +338,18 @@ class RENDER_PT_POV_antialias(RenderButtonsPanel, Panel):
     """Use this class to define pov antialiasing buttons."""
 
     bl_label = "Anti-Aliasing"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         prefs = bpy.context.preferences.addons[__package__].preferences
         scene = context.scene
-        if prefs.branch_feature_set_povray != 'uberpov' and scene.pov.antialias_method == '2':
-            self.layout.prop(scene.pov, "antialias_enable", text="", icon='ERROR')
+        if prefs.branch_feature_set_povray != "uberpov" and scene.pov.antialias_method == "2":
+            self.layout.prop(scene.pov, "antialias_enable", text="", icon="ERROR")
         elif scene.pov.antialias_enable:
-            self.layout.prop(scene.pov, "antialias_enable", text="", icon='ANTIALIASED')
+            self.layout.prop(scene.pov, "antialias_enable", text="", icon="ANTIALIASED")
         else:
-            self.layout.prop(scene.pov, "antialias_enable", text="", icon='ALIASED')
+            self.layout.prop(scene.pov, "antialias_enable", text="", icon="ALIASED")
 
     def draw(self, context):
         prefs = bpy.context.preferences.addons[__package__].preferences
@@ -275,29 +361,29 @@ class RENDER_PT_POV_antialias(RenderButtonsPanel, Panel):
         row = layout.row()
         row.prop(scene.pov, "antialias_method", text="")
 
-        if prefs.branch_feature_set_povray != 'uberpov' and scene.pov.antialias_method == '2':
+        if prefs.branch_feature_set_povray != "uberpov" and scene.pov.antialias_method == "2":
             uberpov_only_qmc_til_pov38release(layout)
         else:
             no_qmc_fallbacks(row, scene, layout)
-        if prefs.branch_feature_set_povray == 'uberpov':
+        if prefs.branch_feature_set_povray == "uberpov":
             row = layout.row()
             row.prop(scene.pov, "antialias_confidence", text="AA Confidence")
-            row.enabled = scene.pov.antialias_method == '2'
+            row.enabled = scene.pov.antialias_method == "2"
 
 
 class RENDER_PT_POV_radiosity(RenderButtonsPanel, Panel):
     """Use this class to define pov radiosity buttons."""
 
     bl_label = "Diffuse Radiosity"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         scene = context.scene
         if scene.pov.radio_enable:
-            self.layout.prop(scene.pov, "radio_enable", text="", icon='OUTLINER_OB_LIGHTPROBE')
+            self.layout.prop(scene.pov, "radio_enable", text="", icon="OUTLINER_OB_LIGHTPROBE")
         else:
-            self.layout.prop(scene.pov, "radio_enable", text="", icon='LIGHTPROBE_CUBEMAP')
+            self.layout.prop(scene.pov, "radio_enable", text="", icon="LIGHTPROBE_CUBEMAP")
 
     def draw(self, context):
         layout = self.layout
@@ -340,7 +426,7 @@ class RENDER_PT_POV_radiosity(RenderButtonsPanel, Panel):
             col.prop(scene.pov, "radio_subsurface")
 
 
-class POV_RADIOSITY_MT_presets(Menu):
+class RADIOSITY_MT_POV_presets(Menu):
     """Use this class to define pov radiosity presets menu."""
 
     bl_label = "Radiosity Presets"
@@ -352,10 +438,10 @@ class POV_RADIOSITY_MT_presets(Menu):
 class RENDER_OT_POV_radiosity_add_preset(AddPresetBase, Operator):
     """Use this class to define pov radiosity add presets button"""
 
-    '''Add a Radiosity Preset'''
+    """Add a Radiosity Preset"""
     bl_idname = "scene.radiosity_preset_add"
     bl_label = "Add Radiosity Preset"
-    preset_menu = "POV_RADIOSITY_MT_presets"
+    preset_menu = "RADIOSITY_MT_POV_presets"
 
     # variable used for all preset values
     preset_defines = ["scene = bpy.context.scene"]
@@ -391,10 +477,10 @@ def rad_panel_func(self, context):
     layout = self.layout
 
     row = layout.row(align=True)
-    row.menu(POV_RADIOSITY_MT_presets.__name__, text=POV_RADIOSITY_MT_presets.bl_label)
-    row.operator(RENDER_OT_POV_radiosity_add_preset.bl_idname, text="", icon='ADD')
+    row.menu(RADIOSITY_MT_POV_presets.__name__, text=RADIOSITY_MT_POV_presets.bl_label)
+    row.operator(RENDER_OT_POV_radiosity_add_preset.bl_idname, text="", icon="ADD")
     row.operator(
-        RENDER_OT_POV_radiosity_add_preset.bl_idname, text="", icon='REMOVE'
+        RENDER_OT_POV_radiosity_add_preset.bl_idname, text="", icon="REMOVE"
     ).remove_active = True
 
 
@@ -408,27 +494,27 @@ def rad_panel_func(self, context):
 bpy.utils.script_paths(subdir="addons")
 # render_freestyle_svg = os.path.join(bpy.utils.script_paths(subdir="addons"), "render_freestyle_svg.py")
 
-render_freestyle_svg = bpy.context.preferences.addons.get('render_freestyle_svg')
+render_freestyle_svg = bpy.context.preferences.addons.get("render_freestyle_svg")
 # mpath=addon_utils.paths()[0].render_freestyle_svg
 # import mpath
 # from mpath import render_freestyle_svg #= addon_utils.modules(module_cache=['Freestyle SVG Exporter'])
 # from scripts\\addons import render_freestyle_svg
 if check_render_freestyle_svg():
-    '''
+    """
     snippetsWIP
     import myscript
     import importlib
 
     importlib.reload(myscript)
     myscript.main()
-    '''
+    """
     for member in dir(render_freestyle_svg):
         subclass = getattr(render_freestyle_svg, member)
         if hasattr(subclass, "COMPAT_ENGINES"):
-            subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+            subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
             if subclass.bl_idname == "RENDER_PT_SVGExporterPanel":
                 subclass.bl_parent_id = "RENDER_PT_POV_filter"
-                subclass.bl_options = {'HIDE_HEADER'}
+                subclass.bl_options = {"HIDE_HEADER"}
                 # subclass.bl_order = 11
                 print(subclass.bl_info)
 
@@ -439,14 +525,14 @@ class RENDER_PT_POV_filter(RenderButtonsPanel, Panel):
     """Use this class to invoke stuff like Freestyle UI."""
 
     bl_label = "Freestyle"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         with_freestyle = bpy.app.build_options.freestyle
         engine = context.scene.render.engine
-        return with_freestyle and engine == 'POVRAY_RENDER'
+        return with_freestyle and engine == "POVRAY_RENDER"
 
     def draw_header(self, context):
 
@@ -455,10 +541,10 @@ class RENDER_PT_POV_filter(RenderButtonsPanel, Panel):
         layout = self.layout
 
         if rd.use_freestyle:
-            layout.prop(rd, "use_freestyle", text="", icon='LINE_DATA')
+            layout.prop(rd, "use_freestyle", text="", icon="LINE_DATA")
 
         else:
-            layout.prop(rd, "use_freestyle", text="", icon='OUTLINER_OB_IMAGE')
+            layout.prop(rd, "use_freestyle", text="", icon="MOD_LINEART")
 
     def draw(self, context):
         rd = context.scene.render
@@ -472,7 +558,7 @@ class RENDER_PT_POV_filter(RenderButtonsPanel, Panel):
 
         flow.prop(rd, "line_thickness_mode", expand=True)
 
-        if rd.line_thickness_mode == 'ABSOLUTE':
+        if rd.line_thickness_mode == "ABSOLUTE":
             flow.prop(rd, "line_thickness")
 
         # Warning if the Freestyle SVG Exporter addon is not enabled
@@ -508,12 +594,16 @@ class RENDER_PT_POV_filter(RenderButtonsPanel, Panel):
 classes = (
     RENDER_PT_POV_export_settings,
     RENDER_PT_POV_render_settings,
+    RENDER_PT_POV_light_paths,
+    RENDER_PT_POV_film,
+    RENDER_PT_POV_hues,
+    RENDER_PT_POV_pattern_rules,
     RENDER_PT_POV_photons,
     RENDER_PT_POV_antialias,
     RENDER_PT_POV_radiosity,
     RENDER_PT_POV_filter,
     # RENDER_PT_povray_baking,
-    POV_RADIOSITY_MT_presets,
+    RADIOSITY_MT_POV_presets,
     RENDER_OT_POV_radiosity_add_preset,
 )
 
diff --git a/render_povray/render_properties.py b/render_povray/render_properties.py
index 339d1301c..1d084aa1e 100755
--- a/render_povray/render_properties.py
+++ b/render_povray/render_properties.py
@@ -332,15 +332,26 @@ class RenderPovSettingsScene(PropertyGroup):
         default=2.5,
     )
 
+    alpha_filter: FloatProperty(
+        name="Alpha",
+        description="User defined color associated background alpha",
+        min=0.0,
+        max=1.0,
+        soft_min=0.01,
+        soft_max=1.0,
+        default=0.75,
+    )
+
     alpha_mode: EnumProperty(
         name="Alpha",
         description="Representation of alpha information in the RGBA pixels",
         items=(
             ("SKY", "Sky", "Transparent pixels are filled with sky color"),
+            ("STRAIGHT", "Straight", "Transparent pixels are not premultiplied"),
             (
                 "TRANSPARENT",
                 "Transparent",
-                "Transparent, World background is transparent with premultiplied alpha",
+                "World background has user defined  premultiplied alpha",
             ),
         ),
         default="SKY",
@@ -657,9 +668,7 @@ class RenderPovSettingsScene(PropertyGroup):
     )
 
 
-classes = (
-    RenderPovSettingsScene,
-)
+classes = (RenderPovSettingsScene,)
 
 
 def register():
diff --git a/render_povray/scenography.py b/render_povray/scenography.py
index e744b266d..1a5142f6e 100755
--- a/render_povray/scenography.py
+++ b/render_povray/scenography.py
@@ -11,8 +11,8 @@ import bpy
 import os
 from imghdr import what  # imghdr is a python lib to identify image file types
 from math import atan, pi, sqrt, degrees
-from . import df3_library  # for smoke rendering
-from .object_primitives import write_object_modifiers
+from . import voxel_lib  # for smoke rendering
+from .model_primitives import write_object_modifiers
 
 
 # -------- find image texture # used for export_world -------- #
@@ -22,18 +22,18 @@ def image_format(img_f):
     """Identify input image filetypes to transmit to POV."""
     # First use the below explicit extensions to identify image file prospects
     ext = {
-        'JPG': "jpeg",
-        'JPEG': "jpeg",
-        'GIF': "gif",
-        'TGA': "tga",
-        'IFF': "iff",
-        'PPM': "ppm",
-        'PNG': "png",
-        'SYS': "sys",
-        'TIFF': "tiff",
-        'TIF': "tiff",
-        'EXR': "exr",
-        'HDR': "hdr",
+        "JPG": "jpeg",
+        "JPEG": "jpeg",
+        "GIF": "gif",
+        "TGA": "tga",
+        "IFF": "iff",
+        "PPM": "ppm",
+        "PNG": "png",
+        "SYS": "sys",
+        "TIFF": "tiff",
+        "TIF": "tiff",
+        "EXR": "exr",
+        "HDR": "hdr",
     }.get(os.path.splitext(img_f)[-1].upper(), "")
     # Then, use imghdr to really identify the filetype as it can be different
     if not ext:
@@ -48,11 +48,11 @@ def img_map(ts):
     """Translate mapping type from Blender UI to POV syntax and return that string."""
     image_map = ""
     texdata = bpy.data.textures[ts.texture]
-    if ts.mapping == 'FLAT':
+    if ts.mapping == "FLAT":
         image_map = "map_type 0 "
-    elif ts.mapping == 'SPHERE':
+    elif ts.mapping == "SPHERE":
         image_map = "map_type 1 "
-    elif ts.mapping == 'TUBE':
+    elif ts.mapping == "TUBE":
         image_map = "map_type 2 "
 
     # map_type 3 and 4 in development (?) (ENV in pov 3.8)
@@ -63,7 +63,7 @@ def img_map(ts):
     #    image_map = " map_type 4 "
     if ts.use_interpolation:  # Available if image sampling class reactivated?
         image_map += " interpolate 2 "
-    if texdata.extension == 'CLIP':
+    if texdata.extension == "CLIP":
         image_map += " once "
     # image_map += "}"
     # if ts.mapping=='CUBE':
@@ -120,16 +120,16 @@ def img_map_bg(wts):
     tex = bpy.data.textures[wts.texture]
     image_mapBG = ""
     # texture_coords refers to the mapping of world textures:
-    if wts.texture_coords in ['VIEW', 'GLOBAL']:
+    if wts.texture_coords in ["VIEW", "GLOBAL"]:
         image_mapBG = " map_type 0 "
-    elif wts.texture_coords == 'ANGMAP':
+    elif wts.texture_coords == "ANGMAP":
         image_mapBG = " map_type 1 "
-    elif wts.texture_coords == 'TUBE':
+    elif wts.texture_coords == "TUBE":
         image_mapBG = " map_type 2 "
 
     if tex.use_interpolation:
         image_mapBG += " interpolate 2 "
-    if tex.extension == 'CLIP':
+    if tex.extension == "CLIP":
         image_mapBG += " once "
     # image_mapBG += "}"
     # if wts.mapping == 'CUBE':
@@ -152,7 +152,7 @@ def path_image(image):
 # -----------------------------------------------------------------------------
 
 
-def export_camera(scene, global_matrix, render, tab_write):
+def export_camera(file, scene, global_matrix, render, tab_write):
     """Translate camera from Blender UI to POV syntax and write to exported file."""
     camera = scene.camera
 
@@ -163,91 +163,103 @@ def export_camera(scene, global_matrix, render, tab_write):
 
     # compute resolution
     q_size = render.resolution_x / render.resolution_y
-    tab_write("#declare camLocation  = <%.6f, %.6f, %.6f>;\n" % matrix.translation[:])
+    tab_write(file, "#declare camLocation  = <%.6f, %.6f, %.6f>;\n" % matrix.translation[:])
     tab_write(
-        "#declare camLookAt = <%.6f, %.6f, %.6f>;\n"
-        % tuple([degrees(e) for e in matrix.to_3x3().to_euler()])
+        file,
+        (
+            "#declare camLookAt = <%.6f, %.6f, %.6f>;\n"
+            % tuple(degrees(e) for e in matrix.to_3x3().to_euler())
+        ),
     )
 
-    tab_write("camera {\n")
-    if scene.pov.baking_enable and active_object and active_object.type == 'MESH':
-        tab_write("mesh_camera{ 1 3\n")  # distribution 3 is what we want here
-        tab_write("mesh{%s}\n" % active_object.name)
-        tab_write("}\n")
-        tab_write("location <0,0,.01>")
-        tab_write("direction <0,0,-1>")
+    tab_write(file, "camera {\n")
+    if scene.pov.baking_enable and active_object and active_object.type == "MESH":
+        tab_write(file, "mesh_camera{ 1 3\n")  # distribution 3 is what we want here
+        tab_write(file, "mesh{%s}\n" % active_object.name)
+        tab_write(file, "}\n")
+        tab_write(file, "location <0,0,.01>")
+        tab_write(file, "direction <0,0,-1>")
 
     else:
-        if camera.data.type == 'ORTHO':
+        if camera.data.type == "ORTHO":
             # XXX todo: track when SensorHeightRatio was added to see if needed (not used)
             sensor_height_ratio = (
                 render.resolution_x * camera.data.ortho_scale / render.resolution_y
             )
-            tab_write("orthographic\n")
+            tab_write(file, "orthographic\n")
             # Blender angle is radian so should be converted to degrees:
             # % (camera.data.angle * (180.0 / pi) )
             # but actually argument is not compulsory after angle in pov ortho mode
-            tab_write("angle\n")
-            tab_write("right <%6f, 0, 0>\n" % -camera.data.ortho_scale)
-            tab_write("location  <0, 0, 0>\n")
-            tab_write("look_at  <0, 0, -1>\n")
-            tab_write("up <0, %6f, 0>\n" % (camera.data.ortho_scale / q_size))
-
-        elif camera.data.type == 'PANO':
-            tab_write("panoramic\n")
-            tab_write("location  <0, 0, 0>\n")
-            tab_write("look_at  <0, 0, -1>\n")
-            tab_write("right <%s, 0, 0>\n" % -q_size)
-            tab_write("up <0, 1, 0>\n")
-            tab_write("angle  %f\n" % (360.0 * atan(16.0 / camera.data.lens) / pi))
-        elif camera.data.type == 'PERSP':
+            tab_write(file, "angle\n")
+            tab_write(file, "right <%6f, 0, 0>\n" % -camera.data.ortho_scale)
+            tab_write(file, "location  <0, 0, 0>\n")
+            tab_write(file, "look_at  <0, 0, -1>\n")
+            tab_write(file, "up <0, %6f, 0>\n" % (camera.data.ortho_scale / q_size))
+
+        elif camera.data.type == "PANO":
+            tab_write(file, "panoramic\n")
+            tab_write(file, "location  <0, 0, 0>\n")
+            tab_write(file, "look_at  <0, 0, -1>\n")
+            tab_write(file, "right <%s, 0, 0>\n" % -q_size)
+            tab_write(file, "up <0, 1, 0>\n")
+            tab_write(file, "angle  %f\n" % (360.0 * atan(16.0 / camera.data.lens) / pi))
+        elif camera.data.type == "PERSP":
             # Standard camera otherwise would be default in pov
-            tab_write("location  <0, 0, 0>\n")
-            tab_write("look_at  <0, 0, -1>\n")
-            tab_write("right <%s, 0, 0>\n" % -q_size)
-            tab_write("up <0, 1, 0>\n")
+            tab_write(file, "location  <0, 0, 0>\n")
+            tab_write(file, "look_at  <0, 0, -1>\n")
+            tab_write(file, "right <%s, 0, 0>\n" % -q_size)
+            tab_write(file, "up <0, 1, 0>\n")
             tab_write(
+                file,
                 "angle  %f\n"
-                % (2 * atan(camera.data.sensor_width / 2 / camera.data.lens) * 180.0 / pi)
+                % (2 * atan(camera.data.sensor_width / 2 / camera.data.lens) * 180.0 / pi),
             )
 
         tab_write(
-            "rotate  <%.6f, %.6f, %.6f>\n" % tuple([degrees(e) for e in matrix.to_3x3().to_euler()])
+            file,
+            "rotate  <%.6f, %.6f, %.6f>\n" % tuple(degrees(e) for e in matrix.to_3x3().to_euler()),
         )
-        tab_write("translate <%.6f, %.6f, %.6f>\n" % matrix.translation[:])
+
+        tab_write(file, "translate <%.6f, %.6f, %.6f>\n" % matrix.translation[:])
         if camera.data.dof.use_dof and (focal_point != 0 or camera.data.dof.focus_object):
-            tab_write("aperture %.3g\n" % (1 / (camera.data.dof.aperture_fstop * 10000) * 1000))
             tab_write(
+                file, "aperture %.3g\n" % (1 / (camera.data.dof.aperture_fstop * 10000) * 1000)
+            )
+            tab_write(
+                file,
                 "blur_samples %d %d\n"
-                % (camera.data.pov.dof_samples_min, camera.data.pov.dof_samples_max)
+                % (camera.data.pov.dof_samples_min, camera.data.pov.dof_samples_max),
             )
-            tab_write("variance 1/%d\n" % camera.data.pov.dof_variance)
-            tab_write("confidence %.3g\n" % camera.data.pov.dof_confidence)
+            tab_write(file, "variance 1/%d\n" % camera.data.pov.dof_variance)
+            tab_write(file, "confidence %.3g\n" % camera.data.pov.dof_confidence)
             if camera.data.dof.focus_object:
                 focal_ob = scene.objects[camera.data.dof.focus_object.name]
                 matrix_blur = global_matrix @ focal_ob.matrix_world
-                tab_write("focal_point <%.4f,%.4f,%.4f>\n" % matrix_blur.translation[:])
+                tab_write(file, "focal_point <%.4f,%.4f,%.4f>\n" % matrix_blur.translation[:])
             else:
-                tab_write("focal_point <0, 0, %f>\n" % focal_point)
+                tab_write(file, "focal_point <0, 0, %f>\n" % focal_point)
     if camera.data.pov.normal_enable:
         tab_write(
+            file,
             "normal {%s %.4f turbulence %.4f scale %.4f}\n"
             % (
                 camera.data.pov.normal_patterns,
                 camera.data.pov.cam_normal,
                 camera.data.pov.turbulence,
                 camera.data.pov.scale,
-            )
+            ),
         )
-    tab_write("}\n")
+    tab_write(file, "}\n")
 
 
 exported_lights_count = 0
 
 
-def export_lights(lamps, file, scene, global_matrix, write_matrix, tab_write):
+def export_lights(lamps, file, scene, global_matrix, tab_write):
     """Translate lights from Blender UI to POV syntax and write to exported file."""
 
+    from .render import write_matrix, tab_write
+
     # Incremented after each lamp export to declare its target
     # currently used for Fresnel diffuse shader as their slope vector:
     global exported_lights_count
@@ -261,60 +273,62 @@ def export_lights(lamps, file, scene, global_matrix, write_matrix, tab_write):
         # any way to directly get bpy_prop_array as tuple?
         color = tuple(lamp.color)
 
-        tab_write("light_source {\n")
-        tab_write("< 0,0,0 >\n")
-        tab_write("color srgb<%.3g, %.3g, %.3g>\n" % color)
+        tab_write(file, "light_source {\n")
+        tab_write(file, "< 0,0,0 >\n")
+        tab_write(file, "color srgb<%.3g, %.3g, %.3g>\n" % color)
 
-        if lamp.type == 'POINT':
+        if lamp.type == "POINT":
             pass
-        elif lamp.type == 'SPOT':
-            tab_write("spotlight\n")
+        elif lamp.type == "SPOT":
+            tab_write(file, "spotlight\n")
 
             # Falloff is the main radius from the centre line
-            tab_write("falloff %.2f\n" % (degrees(lamp.spot_size) / 2.0))  # 1 TO 179 FOR BOTH
-            tab_write("radius %.6f\n" % ((degrees(lamp.spot_size) / 2.0) * (1.0 - lamp.spot_blend)))
+            tab_write(file, "falloff %.2f\n" % (degrees(lamp.spot_size) / 2.0))  # 1 TO 179 FOR BOTH
+            tab_write(
+                file, "radius %.6f\n" % ((degrees(lamp.spot_size) / 2.0) * (1.0 - lamp.spot_blend))
+            )
 
             # Blender does not have a tightness equivalent, 0 is most like blender default.
-            tab_write("tightness 0\n")  # 0:10f
+            tab_write(file, "tightness 0\n")  # 0:10f
 
-            tab_write("point_at  <0, 0, -1>\n")
+            tab_write(file, "point_at  <0, 0, -1>\n")
             if lamp.pov.use_halo:
-                tab_write("looks_like{\n")
-                tab_write("sphere{<0,0,0>,%.6f\n" % lamp.distance)
-                tab_write("hollow\n")
-                tab_write("material{\n")
-                tab_write("texture{\n")
-                tab_write("pigment{rgbf<1,1,1,%.4f>}\n" % (lamp.pov.halo_intensity * 5.0))
-                tab_write("}\n")
-                tab_write("interior{\n")
-                tab_write("media{\n")
-                tab_write("emission 1\n")
-                tab_write("scattering {1, 0.5}\n")
-                tab_write("density{\n")
-                tab_write("spherical\n")
-                tab_write("color_map{\n")
-                tab_write("[0.0 rgb <0,0,0>]\n")
-                tab_write("[0.5 rgb <1,1,1>]\n")
-                tab_write("[1.0 rgb <1,1,1>]\n")
-                tab_write("}\n")
-                tab_write("}\n")
-                tab_write("}\n")
-                tab_write("}\n")
-                tab_write("}\n")
-                tab_write("}\n")
-                tab_write("}\n")
-        elif lamp.type == 'SUN':
-            tab_write("parallel\n")
-            tab_write("point_at  <0, 0, -1>\n")  # *must* be after 'parallel'
-
-        elif lamp.type == 'AREA':
-            tab_write("fade_distance %.6f\n" % (lamp.distance / 2.0))
+                tab_write(file, "looks_like{\n")
+                tab_write(file, "sphere{<0,0,0>,%.6f\n" % lamp.distance)
+                tab_write(file, "hollow\n")
+                tab_write(file, "material{\n")
+                tab_write(file, "texture{\n")
+                tab_write(file, "pigment{rgbf<1,1,1,%.4f>}\n" % (lamp.pov.halo_intensity * 5.0))
+                tab_write(file, "}\n")
+                tab_write(file, "interior{\n")
+                tab_write(file, "media{\n")
+                tab_write(file, "emission 1\n")
+                tab_write(file, "scattering {1, 0.5}\n")
+                tab_write(file, "density{\n")
+                tab_write(file, "spherical\n")
+                tab_write(file, "color_map{\n")
+                tab_write(file, "[0.0 rgb <0,0,0>]\n")
+                tab_write(file, "[0.5 rgb <1,1,1>]\n")
+                tab_write(file, "[1.0 rgb <1,1,1>]\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
+        elif lamp.type == "SUN":
+            tab_write(file, "parallel\n")
+            tab_write(file, "point_at  <0, 0, -1>\n")  # *must* be after 'parallel'
+
+        elif lamp.type == "AREA":
+            tab_write(file, "fade_distance %.6f\n" % (lamp.distance / 2.0))
             # Area lights have no falloff type, so always use blenders lamp quad equivalent
             # for those?
-            tab_write("fade_power %d\n" % 2)
+            tab_write(file, "fade_power %d\n" % 2)
             size_x = lamp.size
             samples_x = lamp.pov.shadow_ray_samples_x
-            if lamp.shape == 'SQUARE':
+            if lamp.shape == "SQUARE":
                 size_y = size_x
                 samples_y = samples_x
             else:
@@ -322,40 +336,42 @@ def export_lights(lamps, file, scene, global_matrix, write_matrix, tab_write):
                 samples_y = lamp.pov.shadow_ray_samples_y
 
             tab_write(
-                "area_light <%.6f,0,0>,<0,%.6f,0> %d, %d\n" % (size_x, size_y, samples_x, samples_y)
+                file,
+                "area_light <%.6f,0,0>,<0,%.6f,0> %d, %d\n"
+                % (size_x, size_y, samples_x, samples_y),
             )
-            tab_write("area_illumination\n")
-            if lamp.pov.shadow_ray_sample_method == 'CONSTANT_JITTERED':
+            tab_write(file, "area_illumination\n")
+            if lamp.pov.shadow_ray_sample_method == "CONSTANT_JITTERED":
                 if lamp.pov.use_jitter:
-                    tab_write("jitter\n")
+                    tab_write(file, "jitter\n")
             else:
-                tab_write("adaptive 1\n")
-                tab_write("jitter\n")
+                tab_write(file, "adaptive 1\n")
+                tab_write(file, "jitter\n")
 
         # No shadow checked either at global or light level:
-        if not scene.pov.use_shadows or (lamp.pov.shadow_method == 'NOSHADOW'):
-            tab_write("shadowless\n")
+        if not scene.pov.use_shadows or (lamp.pov.shadow_method == "NOSHADOW"):
+            tab_write(file, "shadowless\n")
 
         # Sun shouldn't be attenuated. Area lights have no falloff attribute so they
         # are put to type 2 attenuation a little higher above.
-        if lamp.type not in {'SUN', 'AREA'}:
-            if lamp.falloff_type == 'INVERSE_SQUARE':
-                tab_write("fade_distance %.6f\n" % (sqrt(lamp.distance / 2.0)))
-                tab_write("fade_power %d\n" % 2)  # Use blenders lamp quad equivalent
-            elif lamp.falloff_type == 'INVERSE_LINEAR':
-                tab_write("fade_distance %.6f\n" % (lamp.distance / 2.0))
-                tab_write("fade_power %d\n" % 1)  # Use blenders lamp linear
-            elif lamp.falloff_type == 'CONSTANT':
-                tab_write("fade_distance %.6f\n" % (lamp.distance / 2.0))
-                tab_write("fade_power %d\n" % 3)
+        if lamp.type not in {"SUN", "AREA"}:
+            if lamp.falloff_type == "INVERSE_SQUARE":
+                tab_write(file, "fade_distance %.6f\n" % (sqrt(lamp.distance / 2.0)))
+                tab_write(file, "fade_power %d\n" % 2)  # Use blenders lamp quad equivalent
+            elif lamp.falloff_type == "INVERSE_LINEAR":
+                tab_write(file, "fade_distance %.6f\n" % (lamp.distance / 2.0))
+                tab_write(file, "fade_power %d\n" % 1)  # Use blenders lamp linear
+            elif lamp.falloff_type == "CONSTANT":
+                tab_write(file, "fade_distance %.6f\n" % (lamp.distance / 2.0))
+                tab_write(file, "fade_power %d\n" % 3)
                 # Use blenders lamp constant equivalent no attenuation.
             # Using Custom curve for fade power 3 for now.
-            elif lamp.falloff_type == 'CUSTOM_CURVE':
-                tab_write("fade_power %d\n" % 4)
+            elif lamp.falloff_type == "CUSTOM_CURVE":
+                tab_write(file, "fade_power %d\n" % 4)
 
-        write_matrix(matrix)
+        write_matrix(file, matrix)
 
-        tab_write("}\n")
+        tab_write(file, "}\n")
 
         # v(A,B) rotates vector A about origin by vector B.
         file.write(
@@ -372,190 +388,224 @@ def export_lights(lamps, file, scene, global_matrix, write_matrix, tab_write):
         )
 
 
-def export_world(world, scene, global_matrix, tab_write):
-    """write world as POV backgrounbd and sky_sphere to exported file """
+def export_world(file, world, scene, global_matrix, tab_write):
+    """write world as POV background and sky_sphere to exported file"""
     render = scene.pov
+    agnosticrender = scene.render
     camera = scene.camera
     # matrix = global_matrix @ camera.matrix_world  # view dependant for later use NOT USED
     if not world:
         return
 
     # These lines added to get sky gradient (visible with PNG output)
-    if world:
-        # For simple flat background:
-        if not world.pov.use_sky_blend:
+
+    # For simple flat background:
+    if not world.pov.use_sky_blend:
+        # No alpha with Sky option:
+        if render.alpha_mode == "SKY" and not agnosticrender.film_transparent:
+            tab_write(
+                file, "background {rgbt<%.3g, %.3g, %.3g, 0>}\n" % (world.pov.horizon_color[:])
+            )
+
+        elif render.alpha_mode == "STRAIGHT" or agnosticrender.film_transparent:
+            tab_write(
+                file, "background {rgbt<%.3g, %.3g, %.3g, 1>}\n" % (world.pov.horizon_color[:])
+            )
+        else:
             # Non fully transparent background could premultiply alpha and avoid
-            # anti-aliasing display issue:
-            if render.alpha_mode == 'TRANSPARENT':
-                tab_write(
-                    "background {rgbt<%.3g, %.3g, %.3g, 0.75>}\n" % (world.pov.horizon_color[:])
-                )
-            # Currently using no alpha with Sky option:
-            elif render.alpha_mode == 'SKY':
-                tab_write("background {rgbt<%.3g, %.3g, %.3g, 0>}\n" % (world.pov.horizon_color[:]))
-            # StraightAlpha:
-            # XXX Does not exists anymore
-            # else:
-            # tab_write("background {rgbt<%.3g, %.3g, %.3g, 1>}\n" % (world.pov.horizon_color[:]))
-
-        world_tex_count = 0
-        # For Background image textures
-        for t in world.pov_texture_slots:  # risk to write several sky_spheres but maybe ok.
-            if t:
-                tex = bpy.data.textures[t.texture]
-            if tex.type is not None:
-                world_tex_count += 1
-                # XXX No enable checkbox for world textures yet (report it?)
-                # if t and tex.type == 'IMAGE' and t.use:
-                if tex.type == 'IMAGE':
-                    image_filename = path_image(tex.image)
-                    if tex.image.filepath != image_filename:
-                        tex.image.filepath = image_filename
-                    if image_filename != "" and t.use_map_blend:
-                        textures_blend = image_filename
-                        # colvalue = t.default_value
-                        t_blend = t
-
-                    # Commented below was an idea to make the Background image oriented as camera
-                    # taken here:
-                    # http://news.pov.org/pov.newusers/thread/%3Cweb.4a5cddf4e9c9822ba2f93e20@news.pov.org%3E/
-                    # Replace 4/3 by the ratio of each image found by some custom or existing
-                    # function
-                    # mapping_blend = (" translate <%.4g,%.4g,%.4g> rotate z*degrees" \
-                    #                "(atan((camLocation - camLookAt).x/(camLocation - " \
-                    #                "camLookAt).y)) rotate x*degrees(atan((camLocation - " \
-                    #                "camLookAt).y/(camLocation - camLookAt).z)) rotate y*" \
-                    #                "degrees(atan((camLocation - camLookAt).z/(camLocation - " \
-                    #                "camLookAt).x)) scale <%.4g,%.4g,%.4g>b" % \
-                    #                (t_blend.offset.x / 10 , t_blend.offset.y / 10 ,
-                    #                 t_blend.offset.z / 10, t_blend.scale.x ,
-                    #                 t_blend.scale.y , t_blend.scale.z))
-                    # using camera rotation valuesdirectly from blender seems much easier
-                    if t_blend.texture_coords == 'ANGMAP':
-                        mapping_blend = ""
-                    else:
-                        # POV-Ray "scale" is not a number of repetitions factor, but its
-                        # inverse, a standard scale factor.
-                        # 0.5 Offset is needed relatively to scale because center of the
-                        # UV scale is 0.5,0.5 in blender and 0,0 in POV
-                        # Further Scale by 2 and translate by -1 are
-                        # required for the sky_sphere not to repeat
-
-                        mapping_blend = (
-                            "scale 2 scale <%.4g,%.4g,%.4g> translate -1 "
-                            "translate <%.4g,%.4g,%.4g> rotate<0,0,0> "
-                            % (
-                                (1.0 / t_blend.scale.x),
-                                (1.0 / t_blend.scale.y),
-                                (1.0 / t_blend.scale.z),
-                                0.5 - (0.5 / t_blend.scale.x) - t_blend.offset.x,
-                                0.5 - (0.5 / t_blend.scale.y) - t_blend.offset.y,
-                                t_blend.offset.z,
-                            )
-                        )
+            # anti-aliasing display issue
+            tab_write(
+                file,
+                "background {rgbft<%.3g, %.3g, %.3g, %.3g, 0>}\n"
+                % (
+                    world.pov.horizon_color[0],
+                    world.pov.horizon_color[1],
+                    world.pov.horizon_color[2],
+                    render.alpha_filter,
+                ),
+            )
 
-                        # The initial position and rotation of the pov camera is probably creating
-                        # the rotation offset should look into it someday but at least background
-                        # won't rotate with the camera now.
-                    # Putting the map on a plane would not introduce the skysphere distortion and
-                    # allow for better image scale matching but also some waay to chose depth and
-                    # size of the plane relative to camera.
-                    tab_write("sky_sphere {\n")
-                    tab_write("pigment {\n")
-                    tab_write(
-                        "image_map{%s \"%s\" %s}\n"
-                        % (image_format(textures_blend), textures_blend, img_map_bg(t_blend))
+    world_tex_count = 0
+    # For Background image textures
+    for t in world.pov_texture_slots:  # risk to write several sky_spheres but maybe ok.
+        if t:
+            tex = bpy.data.textures[t.texture]
+        if tex.type is not None:
+            world_tex_count += 1
+            # XXX No enable checkbox for world textures yet (report it?)
+            # if t and tex.type == 'IMAGE' and t.use:
+            if tex.type == "IMAGE":
+                image_filename = path_image(tex.image)
+                if tex.image.filepath != image_filename:
+                    tex.image.filepath = image_filename
+                if image_filename != "" and t.use_map_blend:
+                    textures_blend = image_filename
+                    # colvalue = t.default_value
+                    t_blend = t
+
+                # Commented below was an idea to make the Background image oriented as camera
+                # taken here:
+                # http://news.pov.org/pov.newusers/thread/%3Cweb.4a5cddf4e9c9822ba2f93e20@news.pov.org%3E/
+                # Replace 4/3 by the ratio of each image found by some custom or existing
+                # function
+                # mapping_blend = (" translate <%.4g,%.4g,%.4g> rotate z*degrees" \
+                #                "(atan((camLocation - camLookAt).x/(camLocation - " \
+                #                "camLookAt).y)) rotate x*degrees(atan((camLocation - " \
+                #                "camLookAt).y/(camLocation - camLookAt).z)) rotate y*" \
+                #                "degrees(atan((camLocation - camLookAt).z/(camLocation - " \
+                #                "camLookAt).x)) scale <%.4g,%.4g,%.4g>b" % \
+                #                (t_blend.offset.x / 10 , t_blend.offset.y / 10 ,
+                #                 t_blend.offset.z / 10, t_blend.scale.x ,
+                #                 t_blend.scale.y , t_blend.scale.z))
+                # using camera rotation valuesdirectly from blender seems much easier
+                if t_blend.texture_coords == "ANGMAP":
+                    mapping_blend = ""
+                else:
+                    # POV-Ray "scale" is not a number of repetitions factor, but its
+                    # inverse, a standard scale factor.
+                    # 0.5 Offset is needed relatively to scale because center of the
+                    # UV scale is 0.5,0.5 in blender and 0,0 in POV
+                    # Further Scale by 2 and translate by -1 are
+                    # required for the sky_sphere not to repeat
+
+                    mapping_blend = (
+                        "scale 2 scale <%.4g,%.4g,%.4g> translate -1 "
+                        "translate <%.4g,%.4g,%.4g> rotate<0,0,0> "
+                        % (
+                            (1.0 / t_blend.scale.x),
+                            (1.0 / t_blend.scale.y),
+                            (1.0 / t_blend.scale.z),
+                            0.5 - (0.5 / t_blend.scale.x) - t_blend.offset.x,
+                            0.5 - (0.5 / t_blend.scale.y) - t_blend.offset.y,
+                            t_blend.offset.z,
+                        )
                     )
-                    tab_write("}\n")
-                    tab_write("%s\n" % mapping_blend)
-                    # The following layered pigment opacifies to black over the texture for
-                    # transmit below 1 or otherwise adds to itself
-                    tab_write("pigment {rgb 0 transmit %s}\n" % tex.intensity)
-                    tab_write("}\n")
-                    # tab_write("scale 2\n")
-                    # tab_write("translate -1\n")
-
-        # For only Background gradient
-
-        if world_tex_count == 0 and world.pov.use_sky_blend:
-            tab_write("sky_sphere {\n")
-            tab_write("pigment {\n")
-            # maybe Should follow the advice of POV doc about replacing gradient
-            # for skysphere..5.5
-            tab_write("gradient y\n")
-            tab_write("color_map {\n")
-            # XXX Does not exists anymore
-            # if render.alpha_mode == 'STRAIGHT':
-            # tab_write("[0.0 rgbt<%.3g, %.3g, %.3g, 1>]\n" % (world.pov.horizon_color[:]))
-            # tab_write("[1.0 rgbt<%.3g, %.3g, %.3g, 1>]\n" % (world.pov.zenith_color[:]))
-            if render.alpha_mode == 'TRANSPARENT':
-                tab_write("[0.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n" % (world.pov.horizon_color[:]))
-                # aa premult not solved with transmit 1
-                tab_write("[1.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n" % (world.pov.zenith_color[:]))
-            else:
-                tab_write("[0.0 rgbt<%.3g, %.3g, %.3g, 0>]\n" % (world.pov.horizon_color[:]))
-                tab_write("[1.0 rgbt<%.3g, %.3g, %.3g, 0>]\n" % (world.pov.zenith_color[:]))
-            tab_write("}\n")
-            tab_write("}\n")
-            tab_write("}\n")
-            # Sky_sphere alpha (transmit) is not translating into image alpha the same
-            # way as 'background'
-
-        # if world.pov.light_settings.use_indirect_light:
-        #    scene.pov.radio_enable=1
-
-        # Maybe change the above to a function copyInternalRenderer settings when
-        # user pushes a button, then:
-        # scene.pov.radio_enable = world.pov.light_settings.use_indirect_light
-        # and other such translations but maybe this would not be allowed either?
+
+                    # The initial position and rotation of the pov camera is probably creating
+                    # the rotation offset should look into it someday but at least background
+                    # won't rotate with the camera now.
+                # Putting the map on a plane would not introduce the skysphere distortion and
+                # allow for better image scale matching but also some waay to chose depth and
+                # size of the plane relative to camera.
+                tab_write(file, "sky_sphere {\n")
+                tab_write(file, "pigment {\n")
+                tab_write(
+                    file,
+                    'image_map{%s "%s" %s}\n'
+                    % (image_format(textures_blend), textures_blend, img_map_bg(t_blend)),
+                )
+                tab_write(file, "}\n")
+                tab_write(file, "%s\n" % mapping_blend)
+                # The following layered pigment opacifies to black over the texture for
+                # transmit below 1 or otherwise adds to itself
+                tab_write(file, "pigment {rgb 0 transmit %s}\n" % tex.intensity)
+                tab_write(file, "}\n")
+                # tab_write(file, "scale 2\n")
+                # tab_write(file, "translate -1\n")
+
+    # For only Background gradient
+
+    if world_tex_count == 0 and world.pov.use_sky_blend:
+        tab_write(file, "sky_sphere {\n")
+        tab_write(file, "pigment {\n")
+        # maybe Should follow the advice of POV doc about replacing gradient
+        # for skysphere..5.5
+        tab_write(file, "gradient y\n")
+        tab_write(file, "color_map {\n")
+
+        if render.alpha_mode == "TRANSPARENT":
+            tab_write(
+                file,
+                "[0.0 rgbft<%.3g, %.3g, %.3g, %.3g, 0>]\n"
+                % (
+                    world.pov.horizon_color[0],
+                    world.pov.horizon_color[1],
+                    world.pov.horizon_color[2],
+                    render.alpha_filter,
+                ),
+            )
+            tab_write(
+                file,
+                "[1.0 rgbft<%.3g, %.3g, %.3g, %.3g, 0>]\n"
+                % (
+                    world.pov.zenith_color[0],
+                    world.pov.zenith_color[1],
+                    world.pov.zenith_color[2],
+                    render.alpha_filter,
+                ),
+            )
+        if agnosticrender.film_transparent or render.alpha_mode == "STRAIGHT":
+            tab_write(file, "[0.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n" % (world.pov.horizon_color[:]))
+            # aa premult not solved with transmit 1
+            tab_write(file, "[1.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n" % (world.pov.zenith_color[:]))
+        else:
+            tab_write(file, "[0.0 rgbt<%.3g, %.3g, %.3g, 0>]\n" % (world.pov.horizon_color[:]))
+            tab_write(file, "[1.0 rgbt<%.3g, %.3g, %.3g, 0>]\n" % (world.pov.zenith_color[:]))
+        tab_write(file, "}\n")
+        tab_write(file, "}\n")
+        tab_write(file, "}\n")
+        # Sky_sphere alpha (transmit) is not translating into image alpha the same
+        # way as 'background'
+
+    # if world.pov.light_settings.use_indirect_light:
+    #    scene.pov.radio_enable=1
+
+    # Maybe change the above to a function copyInternalRenderer settings when
+    # user pushes a button, then:
+    # scene.pov.radio_enable = world.pov.light_settings.use_indirect_light
+    # and other such translations but maybe this would not be allowed either?
 
     # -----------------------------------------------------------------------------
 
     mist = world.mist_settings
 
     if mist.use_mist:
-        tab_write("fog {\n")
-        if mist.falloff == 'LINEAR':
-            tab_write("distance %.6f\n" % ((mist.start + mist.depth) * 0.368))
-        elif mist.falloff == 'QUADRATIC':  # n**2 or squrt(n)?
-            tab_write("distance %.6f\n" % ((mist.start + mist.depth) ** 2 * 0.368))
-        elif mist.falloff == 'INVERSE_QUADRATIC':  # n**2 or squrt(n)?
-            tab_write("distance %.6f\n" % ((mist.start + mist.depth) ** 2 * 0.368))
+        tab_write(file, "fog {\n")
+        if mist.falloff == "LINEAR":
+            tab_write(file, "distance %.6f\n" % ((mist.start + mist.depth) * 0.368))
+        elif mist.falloff in ["QUADRATIC", "INVERSE_QUADRATIC"]:  # n**2 or squrt(n)?
+            tab_write(file, "distance %.6f\n" % ((mist.start + mist.depth) ** 2 * 0.368))
         tab_write(
+            file,
             "color rgbt<%.3g, %.3g, %.3g, %.3g>\n"
-            % (*world.pov.horizon_color, (1.0 - mist.intensity))
+            % (*world.pov.horizon_color, (1.0 - mist.intensity)),
         )
-        # tab_write("fog_offset %.6f\n" % mist.start) #create a pov property to prepend
-        # tab_write("fog_alt %.6f\n" % mist.height) #XXX right?
-        # tab_write("turbulence 0.2\n")
-        # tab_write("turb_depth 0.3\n")
-        tab_write("fog_type 1\n")  # type2 for height
-        tab_write("}\n")
+        # tab_write(file, "fog_offset %.6f\n" % mist.start) #create a pov property to prepend
+        # tab_write(file, "fog_alt %.6f\n" % mist.height) #XXX right?
+        # tab_write(file, "turbulence 0.2\n")
+        # tab_write(file, "turb_depth 0.3\n")
+        tab_write(file, "fog_type 1\n")  # type2 for height
+        tab_write(file, "}\n")
     if scene.pov.media_enable:
-        tab_write("media {\n")
+        tab_write(file, "media {\n")
         tab_write(
+            file,
             "scattering { %d, rgb %.12f*<%.4g, %.4g, %.4g>\n"
             % (
                 int(scene.pov.media_scattering_type),
                 scene.pov.media_diffusion_scale,
                 *(scene.pov.media_diffusion_color[:]),
-            )
+            ),
         )
-        if scene.pov.media_scattering_type == '5':
-            tab_write("eccentricity %.3g\n" % scene.pov.media_eccentricity)
-        tab_write("}\n")
+        if scene.pov.media_scattering_type == "5":
+            tab_write(file, "eccentricity %.3g\n" % scene.pov.media_eccentricity)
+        tab_write(file, "}\n")
         tab_write(
+            file,
             "absorption %.12f*<%.4g, %.4g, %.4g>\n"
-            % (scene.pov.media_absorption_scale, *(scene.pov.media_absorption_color[:]))
+            % (scene.pov.media_absorption_scale, *(scene.pov.media_absorption_color[:])),
         )
-        tab_write("\n")
-        tab_write("samples %.d\n" % scene.pov.media_samples)
-        tab_write("}\n")
+        tab_write(file, "\n")
+        tab_write(file, "samples %.d\n" % scene.pov.media_samples)
+        tab_write(file, "}\n")
 
 
 # -----------------------------------------------------------------------------
-def export_rainbows(rainbows, file, scene, global_matrix, write_matrix, tab_write):
-    """write all POV rainbows primitives to exported file """
+def export_rainbows(rainbows, file, scene, global_matrix, tab_write):
+    """write all POV rainbows primitives to exported file"""
+
+    from .render import write_matrix, tab_write
+
     pov_mat_name = "Default_texture"
     for ob in rainbows:
         povdataname = ob.data.name  # enough? XXX not used nor matrix fn?
@@ -588,45 +638,47 @@ def export_rainbows(rainbows, file, scene, global_matrix, write_matrix, tab_writ
 
         # XXX TO CHANGE:
         # formerly:
-        # tab_write("#declare %s = rainbow {\n"%povdataname)
+        # tab_write(file, "#declare %s = rainbow {\n"%povdataname)
 
         # clumsy for now but remove the rainbow from instancing
         # system because not an object. use lamps later instead of meshes
 
         # del data_ref[dataname]
-        tab_write("rainbow {\n")
-
-        tab_write("angle %.4f\n" % angle)
-        tab_write("width %.4f\n" % width)
-        tab_write("distance %.4f\n" % distance)
-        tab_write("arc_angle %.4f\n" % ob.pov.arc_angle)
-        tab_write("falloff_angle %.4f\n" % ob.pov.falloff_angle)
-        tab_write("direction <%.4f,%.4f,%.4f>\n" % rmatrix.translation[:])
-        tab_write("up <%.4f,%.4f,%.4f>\n" % (up[0], up[1], up[2]))
-        tab_write("color_map {\n")
-        tab_write("[0.000  color srgbt<1.0, 0.5, 1.0, 1.0>]\n")
-        tab_write("[0.130  color srgbt<0.5, 0.5, 1.0, 0.9>]\n")
-        tab_write("[0.298  color srgbt<0.2, 0.2, 1.0, 0.7>]\n")
-        tab_write("[0.412  color srgbt<0.2, 1.0, 1.0, 0.4>]\n")
-        tab_write("[0.526  color srgbt<0.2, 1.0, 0.2, 0.4>]\n")
-        tab_write("[0.640  color srgbt<1.0, 1.0, 0.2, 0.4>]\n")
-        tab_write("[0.754  color srgbt<1.0, 0.5, 0.2, 0.6>]\n")
-        tab_write("[0.900  color srgbt<1.0, 0.2, 0.2, 0.7>]\n")
-        tab_write("[1.000  color srgbt<1.0, 0.2, 0.2, 1.0>]\n")
-        tab_write("}\n")
-
-        # tab_write("texture {%s}\n"%pov_mat_name)
+        tab_write(file, "rainbow {\n")
+
+        tab_write(file, "angle %.4f\n" % angle)
+        tab_write(file, "width %.4f\n" % width)
+        tab_write(file, "distance %.4f\n" % distance)
+        tab_write(file, "arc_angle %.4f\n" % ob.pov.arc_angle)
+        tab_write(file, "falloff_angle %.4f\n" % ob.pov.falloff_angle)
+        tab_write(file, "direction <%.4f,%.4f,%.4f>\n" % rmatrix.translation[:])
+        tab_write(file, "up <%.4f,%.4f,%.4f>\n" % (up[0], up[1], up[2]))
+        tab_write(file, "color_map {\n")
+        tab_write(file, "[0.000  color srgbt<1.0, 0.5, 1.0, 1.0>]\n")
+        tab_write(file, "[0.130  color srgbt<0.5, 0.5, 1.0, 0.9>]\n")
+        tab_write(file, "[0.298  color srgbt<0.2, 0.2, 1.0, 0.7>]\n")
+        tab_write(file, "[0.412  color srgbt<0.2, 1.0, 1.0, 0.4>]\n")
+        tab_write(file, "[0.526  color srgbt<0.2, 1.0, 0.2, 0.4>]\n")
+        tab_write(file, "[0.640  color srgbt<1.0, 1.0, 0.2, 0.4>]\n")
+        tab_write(file, "[0.754  color srgbt<1.0, 0.5, 0.2, 0.6>]\n")
+        tab_write(file, "[0.900  color srgbt<1.0, 0.2, 0.2, 0.7>]\n")
+        tab_write(file, "[1.000  color srgbt<1.0, 0.2, 0.2, 1.0>]\n")
+        tab_write(file, "}\n")
+
+        # tab_write(file, "texture {%s}\n"%pov_mat_name)
         write_object_modifiers(ob, file)
-        # tab_write("rotate x*90\n")
+        # tab_write(file, "rotate x*90\n")
         # matrix = global_matrix @ ob.matrix_world
-        # write_matrix(matrix)
-        tab_write("}\n")
+        # write_matrix(file, matrix)
+        tab_write(file, "}\n")
         # continue #Don't render proxy mesh, skip to next object
 
 
-def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, write_matrix):
+def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix):
     """export Blender smoke type fluids to pov media using df3 library"""
 
+    from .render import write_matrix, tab_write
+
     flowtype = -1  # XXX todo: not used yet? should trigger emissive for fire type
     depsgraph = bpy.context.evaluated_depsgraph_get()
     smoke_obj = bpy.data.objects[smoke_obj_name].evaluated_get(depsgraph)
@@ -634,17 +686,17 @@ def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, writ
     smoke_modifier = None
     # Search smoke domain target for smoke modifiers
     for mod in smoke_obj.modifiers:
-        if mod.type == 'FLUID':
-            if mod.fluid_type == 'DOMAIN':
+        if mod.type == "FLUID":
+            if mod.fluid_type == "DOMAIN":
                 domain = smoke_obj
                 smoke_modifier = mod
 
-            elif mod.fluid_type == 'FLOW':
-                if mod.flow_settings.flow_type == 'BOTH':
+            elif mod.fluid_type == "FLOW":
+                if mod.flow_settings.flow_type == "BOTH":
                     flowtype = 2
-                elif mod.flow_settings.flow_type == 'FIRE':
+                elif mod.flow_settings.flow_type == "FIRE":
                     flowtype = 1
-                elif mod.flow_settings.flow_type == 'SMOKE':
+                elif mod.flow_settings.flow_type == "SMOKE":
                     flowtype = 0
     eps = 0.000001  # XXX not used currently. restore from corner case ... zero div?
     if domain is not None:
@@ -667,7 +719,7 @@ def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, writ
         big_res = [
             mod_set.domain_resolution[0],
             mod_set.domain_resolution[1],
-            mod_set.domain_resolution[2]
+            mod_set.domain_resolution[2],
         ]
 
         if mod_set.use_noise:
@@ -746,7 +798,7 @@ def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, writ
 
         # return big_res[0], big_res[1], big_res[2], channeldata
 
-        mydf3 = df3_library.df3(big_res[0], big_res[1], big_res[2])
+        mydf3 = voxel_lib.df3(big_res[0], big_res[1], big_res[2])
         sim_sizeX, sim_sizeY, sim_sizeZ = mydf3.size()
         for x in range(sim_sizeX):
             for y in range(sim_sizeY):
@@ -756,7 +808,7 @@ def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, writ
             mydf3.exportDF3(smoke_path)
         except ZeroDivisionError:
             print("Show smoke simulation in 3D view before export")
-        print('Binary smoke.df3 file written in preview directory')
+        print("Binary smoke.df3 file written in preview directory")
         if comments:
             file.write("\n//--Smoke--\n\n")
 
@@ -772,7 +824,7 @@ def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, writ
         file.write("               scattering{ 1, // Type\n")
         file.write("                  <1,1,1>*0.1\n")
         file.write("                } // end scattering\n")
-        file.write("                density{density_file df3 \"%s\"\n" % smoke_path)
+        file.write('                density{density_file df3 "%s"\n' % smoke_path)
         file.write("                        color_map {\n")
         file.write("                        [0.00 rgb 0]\n")
         file.write("                        [0.05 rgb 0]\n")
@@ -805,7 +857,7 @@ def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix, writ
 
         # We apply object's transformations to get final loc/rot/size in world space!
         # Note: we could combine the two previous transformations with this matrix directly...
-        write_matrix(global_matrix @ smoke_obj.matrix_world)
+        write_matrix(file, global_matrix @ smoke_obj.matrix_world)
 
         # END OF TRANSFORMATIONS
 
diff --git a/render_povray/scenography_gui.py b/render_povray/scenography_gui.py
index 4adc4ade8..097c63571 100755
--- a/render_povray/scenography_gui.py
+++ b/render_povray/scenography_gui.py
@@ -16,7 +16,7 @@ from bl_ui import properties_data_camera
 for member in dir(properties_data_camera):
     subclass = getattr(properties_data_camera, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_data_camera
 
 # -------- Use only a subset of the world panels
@@ -34,7 +34,7 @@ from bl_ui import properties_physics_common
 for member in dir(properties_physics_common):
     subclass = getattr(properties_physics_common, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_common
 
 # Physics Rigid Bodies wrapping every class 'as is'
@@ -43,7 +43,7 @@ from bl_ui import properties_physics_rigidbody
 for member in dir(properties_physics_rigidbody):
     subclass = getattr(properties_physics_rigidbody, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_rigidbody
 
 # Physics Rigid Body Constraint wrapping every class 'as is'
@@ -52,7 +52,7 @@ from bl_ui import properties_physics_rigidbody_constraint
 for member in dir(properties_physics_rigidbody_constraint):
     subclass = getattr(properties_physics_rigidbody_constraint, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_rigidbody_constraint
 
 # Physics Smoke and fluids wrapping every class 'as is'
@@ -61,7 +61,7 @@ from bl_ui import properties_physics_fluid
 for member in dir(properties_physics_fluid):
     subclass = getattr(properties_physics_fluid, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_fluid
 
 # Physics softbody wrapping every class 'as is'
@@ -70,7 +70,7 @@ from bl_ui import properties_physics_softbody
 for member in dir(properties_physics_softbody):
     subclass = getattr(properties_physics_softbody, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_softbody
 
 # Physics Field wrapping every class 'as is'
@@ -79,7 +79,7 @@ from bl_ui import properties_physics_field
 for member in dir(properties_physics_field):
     subclass = getattr(properties_physics_field, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_field
 
 # Physics Cloth wrapping every class 'as is'
@@ -88,7 +88,7 @@ from bl_ui import properties_physics_cloth
 for member in dir(properties_physics_cloth):
     subclass = getattr(properties_physics_cloth, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_cloth
 
 # Physics Dynamic Paint wrapping every class 'as is'
@@ -97,7 +97,7 @@ from bl_ui import properties_physics_dynamicpaint
 for member in dir(properties_physics_dynamicpaint):
     subclass = getattr(properties_physics_dynamicpaint, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_physics_dynamicpaint
 
 from bl_ui import properties_particle
@@ -105,7 +105,7 @@ from bl_ui import properties_particle
 for member in dir(properties_particle):  # add all "particle" panels from blender
     subclass = getattr(properties_particle, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_particle
 
 
@@ -113,10 +113,10 @@ class CameraDataButtonsPanel:
     """Use this class to define buttons from the camera data tab of
     properties window."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "data"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -129,10 +129,10 @@ class WorldButtonsPanel:
     """Use this class to define buttons from the world tab of
     properties window."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "world"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -148,9 +148,9 @@ class CAMERA_PT_POV_cam_dof(CameraDataButtonsPanel, Panel):
     """Use this class for camera depth of field focal blur buttons."""
 
     bl_label = "POV Aperture"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     bl_parent_id = "DATA_PT_camera_dof_aperture"
-    bl_options = {'HIDE_HEADER'}
+    bl_options = {"HIDE_HEADER"}
     # def draw_header(self, context):
     # cam = context.camera
 
@@ -183,7 +183,7 @@ class CAMERA_PT_POV_cam_nor(CameraDataButtonsPanel, Panel):
     """Use this class for camera normal perturbation buttons."""
 
     bl_label = "POV Perturbation"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         cam = context.camera
@@ -207,7 +207,7 @@ class CAMERA_PT_POV_replacement_text(CameraDataButtonsPanel, Panel):
     """Use this class for camera text replacement field."""
 
     bl_label = "Custom POV Code"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
@@ -229,7 +229,7 @@ class WORLD_PT_POV_world(WorldButtonsPanel, Panel):
 
     bl_label = "World"
 
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
@@ -238,8 +238,8 @@ class WORLD_PT_POV_world(WorldButtonsPanel, Panel):
 
         row = layout.row(align=True)
         row.menu(WORLD_MT_POV_presets.__name__, text=WORLD_MT_POV_presets.bl_label)
-        row.operator(WORLD_OT_POV_add_preset.bl_idname, text="", icon='ADD')
-        row.operator(WORLD_OT_POV_add_preset.bl_idname, text="", icon='REMOVE').remove_active = True
+        row.operator(WORLD_OT_POV_add_preset.bl_idname, text="", icon="ADD")
+        row.operator(WORLD_OT_POV_add_preset.bl_idname, text="", icon="REMOVE").remove_active = True
 
         row = layout.row()
         row.prop(world, "use_sky_paper")
@@ -262,8 +262,8 @@ class WORLD_PT_POV_mist(WorldButtonsPanel, Panel):
     """Use this class to define pov mist buttons."""
 
     bl_label = "Mist"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         world = context.world
@@ -337,7 +337,7 @@ class RENDER_PT_POV_media(WorldButtonsPanel, Panel):
     """Use this class to define a pov global atmospheric media buttons."""
 
     bl_label = "Atmosphere Media"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         scene = context.scene
@@ -364,7 +364,7 @@ class RENDER_PT_POV_media(WorldButtonsPanel, Panel):
         col.label(text="Absorption:")
         col.prop(scene.pov, "media_absorption_scale")
         col.prop(scene.pov, "media_absorption_color", text="")
-        if scene.pov.media_scattering_type == '5':
+        if scene.pov.media_scattering_type == "5":
             col = layout.column()
             col.prop(scene.pov, "media_eccentricity", text="Eccentricity")
 
@@ -400,8 +400,8 @@ class PovLightButtonsPanel(properties_data_light.DataButtonsPanel):
     """Use this class to define buttons from the light data tab of
     properties window."""
 
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-    POV_OBJECT_TYPES = {'RAINBOW'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+    POV_OBJECT_TYPES = {"RAINBOW"}
 
     @classmethod
     def poll(cls, context):
@@ -429,8 +429,8 @@ from bl_ui import properties_data_light
 # pass
 
 # Now only These panels are kept
-properties_data_light.DATA_PT_custom_props_light.COMPAT_ENGINES.add('POVRAY_RENDER')
-properties_data_light.DATA_PT_context_light.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_data_light.DATA_PT_custom_props_light.COMPAT_ENGINES.add("POVRAY_RENDER")
+properties_data_light.DATA_PT_context_light.COMPAT_ENGINES.add("POVRAY_RENDER")
 
 
 class LIGHT_PT_POV_preview(PovLightButtonsPanel, Panel):
@@ -464,25 +464,25 @@ class LIGHT_PT_POV_light(PovLightButtonsPanel, Panel):
         sub.prop(light, "color", text="")
         sub.prop(light, "energy")
 
-        if light.type in {'POINT', 'SPOT'}:
+        if light.type in {"POINT", "SPOT"}:
             sub.label(text="Falloff:")
             sub.prop(light, "falloff_type", text="")
             sub.prop(light, "distance")
 
-            if light.falloff_type == 'LINEAR_QUADRATIC_WEIGHTED':
+            if light.falloff_type == "LINEAR_QUADRATIC_WEIGHTED":
                 col.label(text="Attenuation Factors:")
                 sub = col.column(align=True)
                 sub.prop(light, "linear_attenuation", slider=True, text="Linear")
                 sub.prop(light, "quadratic_attenuation", slider=True, text="Quadratic")
 
-            elif light.falloff_type == 'INVERSE_COEFFICIENTS':
+            elif light.falloff_type == "INVERSE_COEFFICIENTS":
                 col.label(text="Inverse Coefficients:")
                 sub = col.column(align=True)
                 sub.prop(light, "constant_coefficient", text="Constant")
                 sub.prop(light, "linear_coefficient", text="Linear")
                 sub.prop(light, "quadratic_coefficient", text="Quadratic")
 
-        if light.type == 'AREA':
+        if light.type == "AREA":
             col.prop(light, "distance")
 
         # restore later as interface to POV light groups ?
@@ -523,11 +523,11 @@ def light_panel_func(self, context):
 
     row = layout.row(align=True)
     row.menu(LIGHT_MT_POV_presets.__name__, text=LIGHT_MT_POV_presets.bl_label)
-    row.operator(LIGHT_OT_POV_add_preset.bl_idname, text="", icon='ADD')
-    row.operator(LIGHT_OT_POV_add_preset.bl_idname, text="", icon='REMOVE').remove_active = True
+    row.operator(LIGHT_OT_POV_add_preset.bl_idname, text="", icon="ADD")
+    row.operator(LIGHT_OT_POV_add_preset.bl_idname, text="", icon="REMOVE").remove_active = True
 
 
-'''#TORECREATE##DEPRECATED#
+"""#TORECREATE##DEPRECATED#
 class LIGHT_PT_POV_sunsky(PovLightButtonsPanel, Panel):
     bl_label = properties_data_light.DATA_PT_sunsky.bl_label
 
@@ -539,7 +539,7 @@ class LIGHT_PT_POV_sunsky(PovLightButtonsPanel, Panel):
 
     draw = properties_data_light.DATA_PT_sunsky.draw
 
-'''
+"""
 
 
 class LIGHT_PT_POV_shadow(PovLightButtonsPanel, Panel):
@@ -567,7 +567,7 @@ class LIGHT_PT_POV_shadow(PovLightButtonsPanel, Panel):
         sub.active = light.pov.use_halo
         sub.prop(light.pov, "halo_intensity", text="Intensity")
 
-        if light.pov.shadow_method == 'NOSHADOW' and light.type == 'AREA':
+        if light.pov.shadow_method == "NOSHADOW" and light.type == "AREA":
             split = layout.split()
 
             col = split.column()
@@ -575,13 +575,13 @@ class LIGHT_PT_POV_shadow(PovLightButtonsPanel, Panel):
 
             sub = col.row(align=True)
 
-            if light.shape == 'SQUARE':
+            if light.shape == "SQUARE":
                 sub.prop(light, "shadow_ray_samples_x", text="Samples")
-            elif light.shape == 'RECTANGLE':
+            elif light.shape == "RECTANGLE":
                 sub.prop(light.pov, "shadow_ray_samples_x", text="Samples X")
                 sub.prop(light.pov, "shadow_ray_samples_y", text="Samples Y")
 
-        if light.pov.shadow_method != 'NOSHADOW':
+        if light.pov.shadow_method != "NOSHADOW":
             split = layout.split()
 
             col = split.column()
@@ -591,25 +591,25 @@ class LIGHT_PT_POV_shadow(PovLightButtonsPanel, Panel):
             # col.prop(light.pov, "use_shadow_layer", text="This Layer Only")
             # col.prop(light.pov, "use_only_shadow")
 
-        if light.pov.shadow_method == 'RAY_SHADOW':
+        if light.pov.shadow_method == "RAY_SHADOW":
             split = layout.split()
 
             col = split.column()
             col.label(text="Sampling:")
 
-            if light.type in {'POINT', 'SUN', 'SPOT'}:
+            if light.type in {"POINT", "SUN", "SPOT"}:
                 sub = col.row()
 
                 sub.prop(light.pov, "shadow_ray_samples_x", text="Samples")
                 # any equivalent in pov?
                 # sub.prop(light, "shadow_soft_size", text="Soft Size")
 
-            elif light.type == 'AREA':
+            elif light.type == "AREA":
                 sub = col.row(align=True)
 
-                if light.shape == 'SQUARE':
+                if light.shape == "SQUARE":
                     sub.prop(light.pov, "shadow_ray_samples_x", text="Samples")
-                elif light.shape == 'RECTANGLE':
+                elif light.shape == "RECTANGLE":
                     sub.prop(light.pov, "shadow_ray_samples_x", text="Samples X")
                     sub.prop(light.pov, "shadow_ray_samples_y", text="Samples Y")
 
@@ -625,7 +625,7 @@ class LIGHT_PT_POV_area(PovLightButtonsPanel, Panel):
     def poll(cls, context):
         lamp = context.light
         engine = context.scene.render.engine
-        return (lamp and lamp.type == 'AREA') and (engine in cls.COMPAT_ENGINES)
+        return (lamp and lamp.type == "AREA") and (engine in cls.COMPAT_ENGINES)
 
     draw = properties_data_light.DATA_PT_area.draw
 
@@ -639,7 +639,7 @@ class LIGHT_PT_POV_spot(PovLightButtonsPanel, Panel):
     def poll(cls, context):
         lamp = context.light
         engine = context.scene.render.engine
-        return (lamp and lamp.type == 'SPOT') and (engine in cls.COMPAT_ENGINES)
+        return (lamp and lamp.type == "SPOT") and (engine in cls.COMPAT_ENGINES)
 
     draw = properties_data_light.DATA_PT_spot.draw
 
@@ -654,7 +654,7 @@ class LIGHT_PT_POV_falloff_curve(PovLightButtonsPanel, Panel):
         engine = context.scene.render.engine
 
         return (
-            lamp and lamp.type in {'POINT', 'SPOT'} and lamp.falloff_type == 'CUSTOM_CURVE'
+            lamp and lamp.type in {"POINT", "SPOT"} and lamp.falloff_type == "CUSTOM_CURVE"
         ) and (engine in cls.COMPAT_ENGINES)
 
     draw = properties_data_light.DATA_PT_falloff_curve.draw
@@ -665,14 +665,14 @@ class OBJECT_PT_POV_rainbow(PovLightButtonsPanel, Panel):
     properties window. inheriting lamp buttons panel class"""
 
     bl_label = "POV-Ray Rainbow"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_options = {'HIDE_HEADER'}
 
     @classmethod
     def poll(cls, context):
         engine = context.scene.render.engine
         obj = context.object
-        return obj and obj.pov.object_as == 'RAINBOW' and (engine in cls.COMPAT_ENGINES)
+        return obj and obj.pov.object_as == "RAINBOW" and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         layout = self.layout
@@ -681,10 +681,10 @@ class OBJECT_PT_POV_rainbow(PovLightButtonsPanel, Panel):
 
         col = layout.column()
 
-        if obj.pov.object_as == 'RAINBOW':
+        if obj.pov.object_as == "RAINBOW":
             if not obj.pov.unlock_parameters:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Exported parameters below", icon='LOCKED'
+                    obj.pov, "unlock_parameters", text="Exported parameters below", icon="LOCKED"
                 )
                 col.label(text="Rainbow projection angle: " + str(obj.data.spot_size))
                 col.label(text="Rainbow width: " + str(obj.data.spot_blend))
@@ -694,7 +694,7 @@ class OBJECT_PT_POV_rainbow(PovLightButtonsPanel, Panel):
 
             else:
                 col.prop(
-                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon='UNLOCKED'
+                    obj.pov, "unlock_parameters", text="Edit exported parameters", icon="UNLOCKED"
                 )
                 col.label(text="3D view proxy may get out of synch")
                 col.active = obj.pov.unlock_parameters
diff --git a/render_povray/scripting.py b/render_povray/scripting.py
index 698dbb5fd..f9aa3391c 100755
--- a/render_povray/scripting.py
+++ b/render_povray/scripting.py
@@ -16,13 +16,13 @@ from math import pi, sqrt
 
 
 def export_custom_code(file):
-    """write all POV user defined custom code to exported file """
+    """write all POV user defined custom code to exported file"""
     # Write CurrentAnimation Frame for use in Custom POV Code
     file.write("#declare CURFRAMENUM = %d;\n" % bpy.context.scene.frame_current)
     # Change path and uncomment to add an animated include file by hand:
-    file.write("//#include \"/home/user/directory/animation_include_file.inc\"\n")
+    file.write('//#include "/home/user/directory/animation_include_file.inc"\n')
     for txt in bpy.data.texts:
-        if txt.pov.custom_code == 'both':
+        if txt.pov.custom_code == "both":
             # Why are the newlines needed?
             file.write("\n")
             file.write(txt.as_string())
@@ -32,23 +32,23 @@ def export_custom_code(file):
 # ----------------------------------- IMPORT
 
 
-class ImportPOV(bpy.types.Operator, ImportHelper):
+class SCENE_OT_POV_Import(bpy.types.Operator, ImportHelper):
     """Load Povray files"""
 
     bl_idname = "import_scene.pov"
     bl_label = "POV-Ray files (.pov/.inc)"
-    bl_options = {'PRESET', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"PRESET", "UNDO"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     # -----------
     # File props.
     files: CollectionProperty(
-        type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}
+        type=bpy.types.OperatorFileListElement, options={"HIDDEN", "SKIP_SAVE"}
     )
-    directory: StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
+    directory: StringProperty(maxlen=1024, subtype="FILE_PATH", options={"HIDDEN", "SKIP_SAVE"})
 
     filename_ext = {".pov", ".inc"}
-    filter_glob: StringProperty(default="*.pov;*.inc", options={'HIDDEN'})
+    filter_glob: StringProperty(default="*.pov;*.inc", options={"HIDDEN"})
 
     import_at_cur: BoolProperty(
         name="Import at Cursor Location", description="Ignore Object Matrix", default=False
@@ -67,7 +67,7 @@ class ImportPOV(bpy.types.Operator, ImportHelper):
         lenverts = None
         lenfaces = None
         suffix = -1
-        name = 'Mesh2_%s' % suffix
+        name = "Mesh2_%s" % suffix
         name_search = False
         verts_search = False
         faces_search = False
@@ -90,15 +90,15 @@ class ImportPOV(bpy.types.Operator, ImportHelper):
             color = None
             for item, value in enumerate(cache):
                 # if value == 'texture': # add more later
-                if value == 'pigment':
+                if value == "pigment":
                     # Todo: create function for all color models.
                     # instead of current pass statements
                     # distinguish srgb from rgb into blend option
-                    if cache[item + 2] in {'rgb', 'srgb'}:
+                    if cache[item + 2] in {"rgb", "srgb"}:
                         pass
-                    elif cache[item + 2] in {'rgbf', 'srgbf'}:
+                    elif cache[item + 2] in {"rgbf", "srgbf"}:
                         pass
-                    elif cache[item + 2] in {'rgbt', 'srgbt'}:
+                    elif cache[item + 2] in {"rgbt", "srgbt"}:
                         try:
                             r, g, b, t = (
                                 float(cache[item + 3]),
@@ -108,25 +108,25 @@ class ImportPOV(bpy.types.Operator, ImportHelper):
                             )
                         except BaseException as e:
                             print(e.__doc__)
-                            print('An exception occurred: {}'.format(e))
+                            print("An exception occurred: {}".format(e))
                             r = g = b = t = float(cache[item + 2])
                         color = (r, g, b, t)
 
-                    elif cache[item + 2] in {'rgbft', 'srgbft'}:
+                    elif cache[item + 2] in {"rgbft", "srgbft"}:
                         pass
 
                     else:
                         pass
 
-            if colors == [] or (colors != [] and color not in colors):
+            if colors == [] or color not in colors:
                 colors.append(color)
                 name = ob.name + "_mat"
                 mat_names.append(name)
                 mat = bpy.data.materials.new(name)
                 mat.diffuse_color = (r, g, b)
-                mat.alpha = 1 - t
-                if mat.alpha != 1:
-                    mat.use_transparency = True
+                mat.pov.alpha = 1 - t
+                if mat.pov.alpha != 1:
+                    mat.pov.use_transparency = True
                 ob.data.materials.append(mat)
 
             else:
@@ -138,361 +138,361 @@ class ImportPOV(bpy.types.Operator, ImportHelper):
             print("Importing file: " + file.name)
             file_pov = self.directory + file.name
             # Ignore any non unicode character
-            for line in open(file_pov, encoding='utf-8', errors='ignore'):
-                string = line.replace("{", " ")
-                string = string.replace("}", " ")
-                string = string.replace("<", " ")
-                string = string.replace(">", " ")
-                string = string.replace(",", " ")
-                lw = string.split()
-                # lenwords = len(lw) # Not used... why written?
-                if lw:
-                    if lw[0] == "object":
-                        write_matrix = True
-                    if write_matrix:
-                        if lw[0] not in {"object", "matrix"}:
-                            index = lw[0]
-                        if lw[0] in {"matrix"}:
-                            value = [
-                                float(lw[1]),
-                                float(lw[2]),
-                                float(lw[3]),
-                                float(lw[4]),
-                                float(lw[5]),
-                                float(lw[6]),
-                                float(lw[7]),
-                                float(lw[8]),
-                                float(lw[9]),
-                                float(lw[10]),
-                                float(lw[11]),
-                                float(lw[12]),
-                            ]
-                            matrixes[index] = value
-                            write_matrix = False
-            for line in open(file_pov, encoding='utf-8', errors='ignore'):
-                S = line.replace("{", " { ")
-                S = S.replace("}", " } ")
-                S = S.replace(",", " ")
-                S = S.replace("<", "")
-                S = S.replace(">", " ")
-                S = S.replace("=", " = ")
-                S = S.replace(";", " ; ")
-                S = S.split()
-                # lenS = len(S) # Not used... why written?
-                for word in S:
-                    # -------- Primitives Import -------- #
-                    if word == 'cone':
-                        cone_search = True
-                        name_search = False
-                    if cone_search:
-                        cache.append(word)
-                        if cache[-1] == '}':
-                            try:
-                                x0 = float(cache[2])
-                                y0 = float(cache[3])
-                                z0 = float(cache[4])
-                                r0 = float(cache[5])
-                                x1 = float(cache[6])
-                                y1 = float(cache[7])
-                                z1 = float(cache[8])
-                                r1 = float(cache[9])
-                                # Y is height in most pov files, not z
-                                bpy.ops.pov.addcone(base=r0, cap=r1, height=(y1 - y0))
-                                ob = context.object
-                                ob.location = (x0, y0, z0)
-                                # ob.scale = (r,r,r)
-                                mat_search(cache)
-                            except ValueError:
-                                pass
-                            cache = []
-                            cone_search = False
-                    if word == 'plane':
-                        plane_search = True
-                        name_search = False
-                    if plane_search:
-                        cache.append(word)
-                        if cache[-1] == '}':
-                            try:
-                                bpy.ops.pov.addplane()
-                                ob = context.object
-                                mat_search(cache)
-                            except ValueError:
-                                pass
-                            cache = []
-                            plane_search = False
-                    if word == 'box':
-                        box_search = True
-                        name_search = False
-                    if box_search:
-                        cache.append(word)
-                        if cache[-1] == '}':
-                            try:
-                                x0 = float(cache[2])
-                                y0 = float(cache[3])
-                                z0 = float(cache[4])
-                                x1 = float(cache[5])
-                                y1 = float(cache[6])
-                                z1 = float(cache[7])
-                                # imported_corner_1=(x0, y0, z0)
-                                # imported_corner_2 =(x1, y1, z1)
-                                center = ((x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2)
-                                bpy.ops.pov.addbox()
-                                ob = context.object
-                                ob.location = center
-                                mat_search(cache)
-
-                            except ValueError:
-                                pass
-                            cache = []
-                            box_search = False
-                    if word == 'cylinder':
-                        cylinder_search = True
-                        name_search = False
-                    if cylinder_search:
-                        cache.append(word)
-                        if cache[-1] == '}':
-                            try:
-                                x0 = float(cache[2])
-                                y0 = float(cache[3])
-                                z0 = float(cache[4])
-                                x1 = float(cache[5])
-                                y1 = float(cache[6])
-                                z1 = float(cache[7])
-                                imported_cyl_loc = (x0, y0, z0)
-                                imported_cyl_loc_cap = (x1, y1, z1)
-
-                                r = float(cache[8])
-
-                                vec = Vector(imported_cyl_loc_cap) - Vector(imported_cyl_loc)
-                                depth = vec.length
-                                rot = Vector((0, 0, 1)).rotation_difference(
-                                    vec
-                                )  # Rotation from Z axis.
-                                trans = rot @ Vector(  # XXX Not used, why written?
-                                    (0, 0, depth / 2)
-                                )  # Such that origin is at center of the base of the cylinder.
-                                # center = ((x0 + x1)/2,(y0 + y1)/2,(z0 + z1)/2)
-                                scale_z = sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2 + (z1 - z0) ** 2) / 2
-                                bpy.ops.pov.addcylinder(
-                                    R=r,
-                                    imported_cyl_loc=imported_cyl_loc,
-                                    imported_cyl_loc_cap=imported_cyl_loc_cap,
-                                )
-                                ob = context.object
-                                ob.location = (x0, y0, z0)
-                                ob.rotation_euler = rot.to_euler()
-                                ob.scale = (1, 1, scale_z)
-
-                                # scale data rather than obj?
-                                # bpy.ops.object.mode_set(mode='EDIT')
-                                # bpy.ops.mesh.reveal()
-                                # bpy.ops.mesh.select_all(action='SELECT')
-                                # bpy.ops.transform.resize(value=(1,1,scale_z), orient_type='LOCAL')
-                                # bpy.ops.mesh.hide(unselected=False)
-                                # bpy.ops.object.mode_set(mode='OBJECT')
-
-                                mat_search(cache)
-
-                            except ValueError:
-                                pass
-                            cache = []
-                            cylinder_search = False
-                    if word == 'sphere':
-                        sphere_search = True
-                        name_search = False
-                    if sphere_search:
-                        cache.append(word)
-                        if cache[-1] == '}':
-                            x = y = z = r = 0
-                            try:
-                                x = float(cache[2])
-                                y = float(cache[3])
-                                z = float(cache[4])
-                                r = float(cache[5])
-
-                            except ValueError:
-                                pass
-                            except BaseException as e:
-                                print(e.__doc__)
-                                print('An exception occurred: {}'.format(e))
-                                x = y = z = float(cache[2])
-                                r = float(cache[3])
-                            bpy.ops.pov.addsphere(R=r, imported_loc=(x, y, z))
-                            ob = context.object
-                            ob.location = (x, y, z)
-                            ob.scale = (r, r, r)
-                            mat_search(cache)
-                            cache = []
-                            sphere_search = False
-                    # -------- End Primitives Import -------- #
-                    if word == '#declare':
-                        name_search = True
-                    if name_search:
-                        cache.append(word)
-                        if word == 'mesh2':
+            with open(file_pov, 'r', encoding='utf-8', errors="ignore") as infile:
+                for line in infile:
+                    string = line.replace("{", " ")
+                    string = string.replace("}", " ")
+                    string = string.replace("<", " ")
+                    string = string.replace(">", " ")
+                    string = string.replace(",", " ")
+                    lw = string.split()
+                    # lenwords = len(lw) # Not used... why written?
+                    if lw:
+                        if lw[0] == "object":
+                            write_matrix = True
+                        if write_matrix:
+                            if lw[0] not in {"object", "matrix"}:
+                                index = lw[0]
+                            if lw[0] in {"matrix"}:
+                                value = [
+                                    float(lw[1]),
+                                    float(lw[2]),
+                                    float(lw[3]),
+                                    float(lw[4]),
+                                    float(lw[5]),
+                                    float(lw[6]),
+                                    float(lw[7]),
+                                    float(lw[8]),
+                                    float(lw[9]),
+                                    float(lw[10]),
+                                    float(lw[11]),
+                                    float(lw[12]),
+                                ]
+                                matrixes[index] = value
+                                write_matrix = False
+            with open(file_pov, 'r', encoding='utf-8', errors="ignore") as infile:
+                for line in infile:
+                    S = line.replace("{", " { ")
+                    S = S.replace("}", " } ")
+                    S = S.replace(",", " ")
+                    S = S.replace("<", "")
+                    S = S.replace(">", " ")
+                    S = S.replace("=", " = ")
+                    S = S.replace(";", " ; ")
+                    S = S.split()
+                    # lenS = len(S) # Not used... why written?
+                    for word in S:
+                        # -------- Primitives Import -------- #
+                        if word == "cone":
+                            cone_search = True
                             name_search = False
-                            if cache[-2] == '=':
-                                name = cache[-3]
-                            else:
-                                suffix += 1
-                            cache = []
-                        if word in {'texture', ';'}:
+                        if cone_search:
+                            cache.append(word)
+                            if cache[-1] == "}":
+                                try:
+                                    x0 = float(cache[2])
+                                    y0 = float(cache[3])
+                                    z0 = float(cache[4])
+                                    r0 = float(cache[5])
+                                    x1 = float(cache[6])
+                                    y1 = float(cache[7])
+                                    z1 = float(cache[8])
+                                    r1 = float(cache[9])
+                                    # Y is height in most pov files, not z
+                                    bpy.ops.pov.addcone(base=r0, cap=r1, height=(y1 - y0))
+                                    ob = context.object
+                                    ob.location = (x0, y0, z0)
+                                    # ob.scale = (r,r,r)
+                                    mat_search(cache)
+                                except ValueError:
+                                    pass
+                                cache = []
+                                cone_search = False
+                        if word == "plane":
+                            plane_search = True
+                            name_search = False
+                        if plane_search:
+                            cache.append(word)
+                            if cache[-1] == "}":
+                                try:
+                                    bpy.ops.pov.addplane()
+                                    ob = context.object
+                                    mat_search(cache)
+                                except ValueError:
+                                    pass
+                                cache = []
+                                plane_search = False
+                        if word == "box":
+                            box_search = True
+                            name_search = False
+                        if box_search:
+                            cache.append(word)
+                            if cache[-1] == "}":
+                                try:
+                                    x0 = float(cache[2])
+                                    y0 = float(cache[3])
+                                    z0 = float(cache[4])
+                                    x1 = float(cache[5])
+                                    y1 = float(cache[6])
+                                    z1 = float(cache[7])
+                                    # imported_corner_1=(x0, y0, z0)
+                                    # imported_corner_2 =(x1, y1, z1)
+                                    center = ((x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2)
+                                    bpy.ops.pov.addbox()
+                                    ob = context.object
+                                    ob.location = center
+                                    mat_search(cache)
+
+                                except ValueError:
+                                    pass
+                                cache = []
+                                box_search = False
+                        if word == "cylinder":
+                            cylinder_search = True
                             name_search = False
-                            cache = []
-                    if word == 'vertex_vectors':
-                        verts_search = True
-                    if verts_search:
-                        cache.append(word)
-                        if word == '}':
-                            verts_search = False
-                            lenverts = cache[2]
-                            cache.pop()
-                            cache.pop(0)
-                            cache.pop(0)
-                            cache.pop(0)
-                            for j in range(int(lenverts)):
-                                x = j * 3
-                                y = (j * 3) + 1
-                                z = (j * 3) + 2
-                                verts.append((float(cache[x]), float(cache[y]), float(cache[z])))
-                            cache = []
-                    # if word == 'face_indices':
-                    # faces_search = True
-                    if word == 'texture_list':  # XXX
-                        tex_search = True  # XXX
-                    if tex_search:  # XXX
-                        if (
-                            word not in {'texture_list', 'texture', '{', '}', 'face_indices'}
-                            and not word.isdigit()
-                        ):  # XXX
-                            pov_mats.append(word)  # XXX
-                    if word == 'face_indices':
-                        tex_search = False  # XXX
-                        faces_search = True
-                    if faces_search:
-                        cache.append(word)
-                        if word == '}':
-                            faces_search = False
-                            lenfaces = cache[2]
-                            cache.pop()
-                            cache.pop(0)
-                            cache.pop(0)
-                            cache.pop(0)
-                            lf = int(lenfaces)
-                            var = int(len(cache) / lf)
-                            for k in range(lf):
-                                if var == 3:
-                                    v0 = k * 3
-                                    v1 = k * 3 + 1
-                                    v2 = k * 3 + 2
-                                    faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
-                                if var == 4:
-                                    v0 = k * 4
-                                    v1 = k * 4 + 1
-                                    v2 = k * 4 + 2
-                                    m = k * 4 + 3
-                                    materials.append((int(cache[m])))
-                                    faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
-                                if var == 6:
-                                    v0 = k * 6
-                                    v1 = k * 6 + 1
-                                    v2 = k * 6 + 2
-                                    m0 = k * 6 + 3
-                                    m1 = k * 6 + 4
-                                    m2 = k * 6 + 5
-                                    materials.append(
-                                        (int(cache[m0]), int(cache[m1]), int(cache[m2]))
+                        if cylinder_search:
+                            cache.append(word)
+                            if cache[-1] == "}":
+                                try:
+                                    x0 = float(cache[2])
+                                    y0 = float(cache[3])
+                                    z0 = float(cache[4])
+                                    x1 = float(cache[5])
+                                    y1 = float(cache[6])
+                                    z1 = float(cache[7])
+                                    imported_cyl_loc = (x0, y0, z0)
+                                    imported_cyl_loc_cap = (x1, y1, z1)
+
+                                    r = float(cache[8])
+
+                                    vec = Vector(imported_cyl_loc_cap) - Vector(imported_cyl_loc)
+                                    depth = vec.length
+                                    rot = Vector((0, 0, 1)).rotation_difference(
+                                        vec
+                                    )  # Rotation from Z axis.
+                                    trans = rot @ Vector(  # XXX Not used, why written?
+                                        (0, 0, depth / 2)
+                                    )  # Such that origin is at center of the base of the cylinder.
+                                    # center = ((x0 + x1)/2,(y0 + y1)/2,(z0 + z1)/2)
+                                    scale_z = sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2 + (z1 - z0) ** 2) / 2
+                                    bpy.ops.pov.addcylinder(
+                                        R=r,
+                                        imported_cyl_loc=imported_cyl_loc,
+                                        imported_cyl_loc_cap=imported_cyl_loc_cap,
                                     )
-                                    faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
-                            # mesh = pov_define_mesh(None, verts, [], faces, name, hide_geometry=False)
-                            # ob = object_utils.object_data_add(context, mesh, operator=None)
-
-                            me = bpy.data.meshes.new(name)  # XXX
-                            ob = bpy.data.objects.new(name, me)  # XXX
-                            bpy.context.collection.objects.link(ob)  # XXX
-                            me.from_pydata(verts, [], faces)  # XXX
-
-                            for mat in bpy.data.materials:  # XXX
-                                blend_mats.append(mat.name)  # XXX
-                            for m_name in pov_mats:  # XXX
-                                if m_name not in blend_mats:  # XXX
-                                    bpy.data.materials.new(m_name)  # XXX
+                                    ob = context.object
+                                    ob.location = (x0, y0, z0)
+                                    # todo: test and search where to add the below currently commented
+                                    # since Blender defers the evaluation until the results are needed.
+                                    # bpy.context.view_layer.update()
+                                    # as explained here: https://docs.blender.org/api/current/info_gotcha.html?highlight=gotcha#no-updates-after-setting-values
+                                    ob.rotation_euler = rot.to_euler()
+                                    ob.scale = (1, 1, scale_z)
+
+                                    # scale data rather than obj?
+                                    # bpy.ops.object.mode_set(mode='EDIT')
+                                    # bpy.ops.mesh.reveal()
+                                    # bpy.ops.mesh.select_all(action='SELECT')
+                                    # bpy.ops.transform.resize(value=(1,1,scale_z), orient_type='LOCAL')
+                                    # bpy.ops.mesh.hide(unselected=False)
+                                    # bpy.ops.object.mode_set(mode='OBJECT')
+
                                     mat_search(cache)
-                                ob.data.materials.append(
-                                    bpy.data.materials[m_name]
-                                )  # XXX
-                            if materials:  # XXX
-                                for idx, val in enumerate(materials):  # XXX
-                                    try:  # XXX
-                                        ob.data.polygons[
-                                            idx
-                                        ].material_index = val  # XXX
-                                    except TypeError:  # XXX
-                                        ob.data.polygons[idx].material_index = int(
-                                            val[0]
-                                        )  # XXX
-
-                            blend_mats = []  # XXX
-                            pov_mats = []  # XXX
-                            materials = []  # XXX
-                            cache = []
+
+                                except ValueError:
+                                    pass
+                                cache = []
+                                cylinder_search = False
+                        if word == "sphere":
+                            sphere_search = True
+                            name_search = False
+                        if sphere_search:
+                            cache.append(word)
+                            if cache[-1] == "}":
+                                x = y = z = r = 0
+                                try:
+                                    x = float(cache[2])
+                                    y = float(cache[3])
+                                    z = float(cache[4])
+                                    r = float(cache[5])
+
+                                except ValueError:
+                                    pass
+                                except BaseException as e:
+                                    print(e.__doc__)
+                                    print("An exception occurred: {}".format(e))
+                                    x = y = z = float(cache[2])
+                                    r = float(cache[3])
+                                bpy.ops.pov.addsphere(R=r, imported_loc=(x, y, z))
+                                ob = context.object
+                                ob.location = (x, y, z)
+                                ob.scale = (r, r, r)
+                                mat_search(cache)
+                                cache = []
+                                sphere_search = False
+                        # -------- End Primitives Import -------- #
+                        if word == "#declare":
                             name_search = True
-                            if name in matrixes and not self.import_at_cur:
-                                global_matrix = Matrix.Rotation(pi / 2.0, 4, 'X')
-                                ob = bpy.context.object
-                                matrix = ob.matrix_world
-                                v = matrixes[name]
-                                matrix[0][0] = v[0]
-                                matrix[1][0] = v[1]
-                                matrix[2][0] = v[2]
-                                matrix[0][1] = v[3]
-                                matrix[1][1] = v[4]
-                                matrix[2][1] = v[5]
-                                matrix[0][2] = v[6]
-                                matrix[1][2] = v[7]
-                                matrix[2][2] = v[8]
-                                matrix[0][3] = v[9]
-                                matrix[1][3] = v[10]
-                                matrix[2][3] = v[11]
-                                matrix = global_matrix * ob.matrix_world
-                                ob.matrix_world = matrix
-                            verts = []
-                            faces = []
-
-                    # if word == 'pigment':
-                    # try:
-                    # #all indices have been incremented once to fit a bad test file
-                    # r,g,b,t = float(S[2]),float(S[3]),float(S[4]),float(S[5])
-                    # color = (r,g,b,t)
-
-                    # except IndexError:
-                    # #all indices have been incremented once to fit alternate test file
-                    # r,g,b,t = float(S[3]),float(S[4]),float(S[5]),float(S[6])
-                    # color = (r,g,b,t)
-                    # except UnboundLocalError:
-                    # # In case no transmit is specified ? put it to 0
-                    # r,g,b,t = float(S[2]),float(S[3]),float(S[4],0)
-                    # color = (r,g,b,t)
-
-                    # except ValueError:
-                    # color = (0.8,0.8,0.8,0)
-                    # pass
-
-                    # if colors == [] or (colors != [] and color not in colors):
-                    # colors.append(color)
-                    # name = ob.name+"_mat"
-                    # mat_names.append(name)
-                    # mat = bpy.data.materials.new(name)
-                    # mat.diffuse_color = (r,g,b)
-                    # mat.alpha = 1-t
-                    # if mat.alpha != 1:
-                    # mat.use_transparency=True
-                    # ob.data.materials.append(mat)
-                    # print (colors)
-                    # else:
-                    # for m in range(len(colors)):
-                    # if color == colors[m]:
-                    # ob.data.materials.append(bpy.data.materials[mat_names[m]])
+                        if name_search:
+                            cache.append(word)
+                            if word == "mesh2":
+                                name_search = False
+                                if cache[-2] == "=":
+                                    name = cache[-3]
+                                else:
+                                    suffix += 1
+                                cache = []
+                            if word in {"texture", ";"}:
+                                name_search = False
+                                cache = []
+                        if word == "vertex_vectors":
+                            verts_search = True
+                        if verts_search:
+                            cache.append(word)
+                            if word == "}":
+                                verts_search = False
+                                lenverts = cache[2]
+                                cache.pop()
+                                cache.pop(0)
+                                cache.pop(0)
+                                cache.pop(0)
+                                for j in range(int(lenverts)):
+                                    x = j * 3
+                                    y = (j * 3) + 1
+                                    z = (j * 3) + 2
+                                    verts.append((float(cache[x]), float(cache[y]), float(cache[z])))
+                                cache = []
+                        # if word == 'face_indices':
+                        # faces_search = True
+                        if word == "texture_list":  # XXX
+                            tex_search = True  # XXX
+                        if tex_search:  # XXX
+                            if (
+                                word not in {"texture_list", "texture", "{", "}", "face_indices"}
+                                and not word.isdigit()
+                            ):  # XXX
+                                pov_mats.append(word)  # XXX
+                        if word == "face_indices":
+                            tex_search = False  # XXX
+                            faces_search = True
+                        if faces_search:
+                            cache.append(word)
+                            if word == "}":
+                                faces_search = False
+                                lenfaces = cache[2]
+                                cache.pop()
+                                cache.pop(0)
+                                cache.pop(0)
+                                cache.pop(0)
+                                lf = int(lenfaces)
+                                var = int(len(cache) / lf)
+                                for k in range(lf):
+                                    if var == 3:
+                                        v0 = k * 3
+                                        v1 = k * 3 + 1
+                                        v2 = k * 3 + 2
+                                        faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
+                                    if var == 4:
+                                        v0 = k * 4
+                                        v1 = k * 4 + 1
+                                        v2 = k * 4 + 2
+                                        m = k * 4 + 3
+                                        materials.append((int(cache[m])))
+                                        faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
+                                    if var == 6:
+                                        v0 = k * 6
+                                        v1 = k * 6 + 1
+                                        v2 = k * 6 + 2
+                                        m0 = k * 6 + 3
+                                        m1 = k * 6 + 4
+                                        m2 = k * 6 + 5
+                                        materials.append(
+                                            (int(cache[m0]), int(cache[m1]), int(cache[m2]))
+                                        )
+                                        faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
+                                # mesh = pov_define_mesh(None, verts, [], faces, name, hide_geometry=False)
+                                # ob = object_utils.object_data_add(context, mesh, operator=None)
+
+                                me = bpy.data.meshes.new(name)  # XXX
+                                ob = bpy.data.objects.new(name, me)  # XXX
+                                bpy.context.collection.objects.link(ob)  # XXX
+                                me.from_pydata(verts, [], faces)  # XXX
+
+                                for mat in bpy.data.materials:  # XXX
+                                    blend_mats.append(mat.name)  # XXX
+                                for m_name in pov_mats:  # XXX
+                                    if m_name not in blend_mats:  # XXX
+                                        bpy.data.materials.new(m_name)  # XXX
+                                        mat_search(cache)
+                                    ob.data.materials.append(bpy.data.materials[m_name])  # XXX
+                                if materials:  # XXX
+                                    for idx, val in enumerate(materials):  # XXX
+                                        try:  # XXX
+                                            ob.data.polygons[idx].material_index = val  # XXX
+                                        except TypeError:  # XXX
+                                            ob.data.polygons[idx].material_index = int(val[0])  # XXX
+
+                                blend_mats = []  # XXX
+                                pov_mats = []  # XXX
+                                materials = []  # XXX
+                                cache = []
+                                name_search = True
+                                if name in matrixes and not self.import_at_cur:
+                                    global_matrix = Matrix.Rotation(pi / 2.0, 4, "X")
+                                    ob = bpy.context.object
+                                    matrix = ob.matrix_world
+                                    v = matrixes[name]
+                                    matrix[0][0] = v[0]
+                                    matrix[1][0] = v[1]
+                                    matrix[2][0] = v[2]
+                                    matrix[0][1] = v[3]
+                                    matrix[1][1] = v[4]
+                                    matrix[2][1] = v[5]
+                                    matrix[0][2] = v[6]
+                                    matrix[1][2] = v[7]
+                                    matrix[2][2] = v[8]
+                                    matrix[0][3] = v[9]
+                                    matrix[1][3] = v[10]
+                                    matrix[2][3] = v[11]
+                                    matrix = global_matrix * ob.matrix_world
+                                    ob.matrix_world = matrix
+                                verts = []
+                                faces = []
+
+                        # if word == 'pigment':
+                        # try:
+                        # #all indices have been incremented once to fit a bad test file
+                        # r,g,b,t = float(S[2]),float(S[3]),float(S[4]),float(S[5])
+                        # color = (r,g,b,t)
+
+                        # except IndexError:
+                        # #all indices have been incremented once to fit alternate test file
+                        # r,g,b,t = float(S[3]),float(S[4]),float(S[5]),float(S[6])
+                        # color = (r,g,b,t)
+                        # except UnboundLocalError:
+                        # # In case no transmit is specified ? put it to 0
+                        # r,g,b,t = float(S[2]),float(S[3]),float(S[4],0)
+                        # color = (r,g,b,t)
+
+                        # except ValueError:
+                        # color = (0.8,0.8,0.8,0)
+                        # pass
+
+                        # if colors == [] or (colors != [] and color not in colors):
+                        # colors.append(color)
+                        # name = ob.name+"_mat"
+                        # mat_names.append(name)
+                        # mat = bpy.data.materials.new(name)
+                        # mat.diffuse_color = (r,g,b)
+                        # mat.pov.alpha = 1-t
+                        # if mat.pov.alpha != 1:
+                        # mat.pov.use_transparency=True
+                        # ob.data.materials.append(mat)
+                        # print (colors)
+                        # else:
+                        # for m in range(len(colors)):
+                        # if color == colors[m]:
+                        # ob.data.materials.append(bpy.data.materials[mat_names[m]])
 
         # To keep Avogadro Camera angle:
         # for obj in bpy.context.view_layer.objects:
@@ -502,12 +502,10 @@ class ImportPOV(bpy.types.Operator, ImportHelper):
         # track.track_axis ="TRACK_NEGATIVE_Z"
         # track.up_axis = "UP_Y"
         # obj.location = (0,0,0)
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
-classes = (
-    ImportPOV,
-)
+classes = (SCENE_OT_POV_Import,)
 
 
 def register():
diff --git a/render_povray/scripting_gui.py b/render_povray/scripting_gui.py
index 0ca2d9491..ed0831010 100755
--- a/render_povray/scripting_gui.py
+++ b/render_povray/scripting_gui.py
@@ -21,8 +21,7 @@ def locate_docpath():
 
     addon_prefs = bpy.context.preferences.addons[__package__].preferences
     # Use the system preference if its set.
-    pov_documents = addon_prefs.docpath_povray
-    if pov_documents:
+    if pov_documents := addon_prefs.docpath_povray:
         if os.path.exists(pov_documents):
             return pov_documents
         # Implicit else, as here return was still not triggered:
@@ -31,7 +30,7 @@ def locate_docpath():
         )
 
     # Windows Only
-    if platform.startswith('win'):
+    if platform.startswith("win"):
         import winreg
 
         try:
@@ -47,7 +46,7 @@ def locate_docpath():
     # search the path all os's
     pov_documents_default = "include"
 
-    os_path_ls = os.getenv("PATH").split(':') + [""]
+    os_path_ls = os.getenv("PATH").split(":") + [""]
 
     for dir_name in os_path_ls:
         pov_documents = os.path.join(dir_name, pov_documents_default)
@@ -63,10 +62,10 @@ class TextButtonsPanel:
     """Use this class to define buttons from the side tab of
     text window."""
 
-    bl_space_type = 'TEXT_EDITOR'
-    bl_region_type = 'UI'
+    bl_space_type = "TEXT_EDITOR"
+    bl_region_type = "UI"
     bl_label = "POV-Ray"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -86,12 +85,12 @@ class TEXT_OT_POV_insert(Operator):
     bl_idname = "text.povray_insert"
     bl_label = "Insert"
 
-    filepath: bpy.props.StringProperty(name="Filepath", subtype='FILE_PATH')
+    filepath: bpy.props.StringProperty(name="Filepath", subtype="FILE_PATH")
 
     @classmethod
     def poll(cls, context):
         text = context.space_data.text
-        return context.area.type == 'TEXT_EDITOR' and text is not None
+        return context.area.type == "TEXT_EDITOR" and text is not None
         # return bpy.ops.text.insert.poll() this Bpy op has no poll()
 
     def execute(self, context):
@@ -103,7 +102,7 @@ class TEXT_OT_POV_insert(Operator):
                 # context.space_data.text.write(file.read())
             if not file.closed:
                 file.close()
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 def validinsert(ext):
@@ -119,7 +118,7 @@ class TEXT_MT_POV_insert(Menu):
 
     def draw(self, context):
         pov_documents = locate_docpath()
-        prop = self.layout.operator("wm.path_open", text="Open folder", icon='FILE_FOLDER')
+        prop = self.layout.operator("wm.path_open", text="Open folder", icon="FILE_FOLDER")
         prop.filepath = pov_documents
         self.layout.separator()
 
@@ -140,15 +139,18 @@ class TEXT_PT_POV_custom_code(TextButtonsPanel, Panel):
     only or adds to 3d scene."""
 
     bl_label = "POV"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
 
         text = context.space_data.text
 
-        pov_documents = locate_docpath()
-        if not pov_documents:
+        if pov_documents := locate_docpath():
+            # print(pov_documents)
+            layout.menu(TEXT_MT_POV_insert.bl_idname)
+
+        else:
             layout.label(text="Please configure ", icon="INFO")
             layout.label(text="default pov include path ")
             layout.label(text="in addon preferences")
@@ -159,24 +161,19 @@ class TEXT_PT_POV_custom_code(TextButtonsPanel, Panel):
                 icon="PREFERENCES",
             ).module = "render_povray"
 
-            # layout.separator()
-        else:
-            # print(pov_documents)
-            layout.menu(TEXT_MT_POV_insert.bl_idname)
-
         if text:
             box = layout.box()
-            box.label(text='Source to render:', icon='RENDER_STILL')
+            box.label(text="Source to render:", icon="RENDER_STILL")
             row = box.row()
             row.prop(text.pov, "custom_code", expand=True)
-            if text.pov.custom_code in {'3dview'}:
-                box.operator("render.render", icon='OUTLINER_DATA_ARMATURE')
-            if text.pov.custom_code in {'text'}:
+            if text.pov.custom_code in {"3dview"}:
+                box.operator("render.render", icon="OUTLINER_DATA_ARMATURE")
+            if text.pov.custom_code in {"text"}:
                 rtext = bpy.context.space_data.text  # is r a typo ? or why written, not used
-                box.operator("text.run", icon='ARMATURE_DATA')
+                box.operator("text.run", icon="ARMATURE_DATA")
             # layout.prop(text.pov, "custom_code")
-            elif text.pov.custom_code in {'both'}:
-                box.operator("render.render", icon='POSE_HLT')
+            elif text.pov.custom_code in {"both"}:
+                box.operator("render.render", icon="POSE_HLT")
                 layout.label(text="Please specify declared", icon="INFO")
                 layout.label(text="items in properties ")
                 # layout.label(text="")
@@ -218,14 +215,14 @@ class VIEW_MT_POV_import(Menu):
 
     def draw(self, context):
         layout = self.layout
-        layout.operator_context = 'INVOKE_REGION_WIN'
+        layout.operator_context = "INVOKE_REGION_WIN"
         layout.operator("import_scene.pov", icon="FORCE_LENNARDJONES")
 
 
 def menu_func_import(self, context):
     """Add the import operator to menu"""
     engine = context.scene.render.engine
-    if engine == 'POVRAY_RENDER':
+    if engine == "POVRAY_RENDER":
         self.layout.operator("import_scene.pov", icon="FORCE_LENNARDJONES")
 
 
diff --git a/render_povray/scripting_properties.py b/render_povray/scripting_properties.py
index 59c373811..cfcaf658f 100755
--- a/render_povray/scripting_properties.py
+++ b/render_povray/scripting_properties.py
@@ -26,9 +26,7 @@ class RenderPovSettingsText(PropertyGroup):
     )
 
 
-classes = (
-    RenderPovSettingsText,
-)
+classes = (RenderPovSettingsText,)
 
 
 def register():
diff --git a/render_povray/shading.py b/render_povray/shading.py
index 67ee8e518..cc61c5b8f 100755
--- a/render_povray/shading.py
+++ b/render_povray/shading.py
@@ -7,102 +7,101 @@
 import bpy
 
 
-def write_object_material_interior(material, ob, tab_write):
+def write_object_material_interior(file, material, ob, tab_write):
     """Translate some object level material from Blender UI (VS data level)
 
     to POV interior{} syntax and write it to exported file.
-    This is called in object_mesh_topology.export_meshes
+    This is called in model_all.objects_loop
     """
     # DH - modified some variables to be function local, avoiding RNA write
     # this should be checked to see if it is functionally correct
 
     # Commented out: always write IOR to be able to use it for SSS, Fresnel reflections...
-    # if material and material.transparency_method == 'RAYTRACE':
-    if material:
-        # But there can be only one!
-        if material.pov_subsurface_scattering.use:  # SSS IOR get highest priority
-            tab_write("interior {\n")
-            tab_write("ior %.6f\n" % material.pov_subsurface_scattering.ior)
-        # Then the raytrace IOR taken from raytrace transparency properties and used for
-        # reflections if IOR Mirror option is checked.
-        elif material.pov.mirror_use_IOR:
-            tab_write("interior {\n")
-            tab_write("ior %.6f\n" % material.pov_raytrace_transparency.ior)
-        elif material.pov.transparency_method == 'Z_TRANSPARENCY':
-            tab_write("interior {\n")
-            tab_write("ior 1.0\n")
-        else:
-            tab_write("interior {\n")
-            tab_write("ior %.6f\n" % material.pov_raytrace_transparency.ior)
+    # if material and material.pov.transparency_method == 'RAYTRACE':
+    if not material:
+        return
+    # implicit if material:
+    # But there can be only one ior!
+    if material.pov_subsurface_scattering.use:  # SSS IOR get highest priority
+        tab_write(file, "interior {\n")
+        tab_write(file, "ior %.6f\n" % material.pov_subsurface_scattering.ior)
+    # Then the raytrace IOR taken from raytrace transparency properties and used for
+    # reflections if IOR Mirror option is checked.
+    elif material.pov.mirror_use_IOR or material.pov.transparency_method != "Z_TRANSPARENCY":
+        tab_write(file, "interior {\n")
+        tab_write(file, "ior %.6f\n" % material.pov_raytrace_transparency.ior)
+    else:
+        tab_write(file, "interior {\n")
+        tab_write(file, "ior 1.0\n")
 
+    pov_fake_caustics = False
+    pov_photons_refraction = False
+    pov_photons_reflection = bool(material.pov.photons_reflection)
+    if not material.pov.refraction_caustics:
         pov_fake_caustics = False
         pov_photons_refraction = False
-        pov_photons_reflection = False
-
-        if material.pov.photons_reflection:
-            pov_photons_reflection = True
-        if not material.pov.refraction_caustics:
-            pov_fake_caustics = False
-            pov_photons_refraction = False
-        elif material.pov.refraction_type == "1":
-            pov_fake_caustics = True
-            pov_photons_refraction = False
-        elif material.pov.refraction_type == "2":
-            pov_fake_caustics = False
-            pov_photons_refraction = True
-
-        # If only Raytrace transparency is set, its IOR will be used for refraction, but user
-        # can set up 'un-physical' fresnel reflections in raytrace mirror parameters.
-        # Last, if none of the above is specified, user can set up 'un-physical' fresnel
-        # reflections in raytrace mirror parameters. And pov IOR defaults to 1.
-        if material.pov.caustics_enable:
-            if pov_fake_caustics:
-                tab_write("caustics %.3g\n" % material.pov.fake_caustics_power)
-            if pov_photons_refraction:
-                # Default of 1 means no dispersion
-                tab_write("dispersion %.6f\n" % material.pov.photons_dispersion)
-                tab_write("dispersion_samples %.d\n" % material.pov.photons_dispersion_samples)
-        # TODO
-        # Other interior args
-        if material.pov.use_transparency and material.pov.transparency_method == 'RAYTRACE':
-            # fade_distance
-            # In Blender this value has always been reversed compared to what tooltip says.
-            # 100.001 rather than 100 so that it does not get to 0
-            # which deactivates the feature in POV
-            tab_write(
-                "fade_distance %.3g\n" % (100.001 - material.pov_raytrace_transparency.depth_max)
-            )
-            # fade_power
-            tab_write("fade_power %.3g\n" % material.pov_raytrace_transparency.falloff)
-            # fade_color
-            tab_write("fade_color <%.3g, %.3g, %.3g>\n" % material.pov.interior_fade_color[:])
-
-        # (variable) dispersion_samples (constant count for now)
-        tab_write("}\n")
-        if material.pov.photons_reflection or material.pov.refraction_type == "2":
-            tab_write("photons{")
-            tab_write("target %.3g\n" % ob.pov.spacing_multiplier)
-            if not ob.pov.collect_photons:
-                tab_write("collect off\n")
-            if pov_photons_refraction:
-                tab_write("refraction on\n")
-            if pov_photons_reflection:
-                tab_write("reflection on\n")
-            tab_write("}\n")
+    elif material.pov.refraction_type == "1":
+        pov_fake_caustics = True
+        pov_photons_refraction = False
+    elif material.pov.refraction_type == "2":
+        pov_fake_caustics = False
+        pov_photons_refraction = True
+
+    # If only Raytrace transparency is set, its IOR will be used for refraction, but user
+    # can set up 'un-physical' fresnel reflections in raytrace mirror parameters.
+    # Last, if none of the above is specified, user can set up 'un-physical' fresnel
+    # reflections in raytrace mirror parameters. And pov IOR defaults to 1.
+    if material.pov.caustics_enable:
+        if pov_fake_caustics:
+            tab_write(file, "caustics %.3g\n" % material.pov.fake_caustics_power)
+        if pov_photons_refraction:
+            # Default of 1 means no dispersion
+            tab_write(file, "dispersion %.6f\n" % material.pov.photons_dispersion)
+            tab_write(file, "dispersion_samples %.d\n" % material.pov.photons_dispersion_samples)
+    # TODO
+    # Other interior args
+    if material.pov.use_transparency and material.pov.transparency_method == "RAYTRACE":
+        # fade_distance
+        # In Blender this value has always been reversed compared to what tooltip says.
+        # 100.001 rather than 100 so that it does not get to 0
+        # which deactivates the feature in POV
+        tab_write(
+            file, "fade_distance %.3g\n" % (100.001 - material.pov_raytrace_transparency.depth_max)
+        )
+        # fade_power
+        tab_write(file, "fade_power %.3g\n" % material.pov_raytrace_transparency.falloff)
+        # fade_color
+        tab_write(file, "fade_color <%.3g, %.3g, %.3g>\n" % material.pov.interior_fade_color[:])
+
+    # (variable) dispersion_samples (constant count for now)
+    tab_write(file, "}\n")
+    if material.pov.photons_reflection or material.pov.refraction_type == "2":
+        tab_write(file, "photons{")
+        tab_write(file, "target %.3g\n" % ob.pov.spacing_multiplier)
+        if not ob.pov.collect_photons:
+            tab_write(file, "collect off\n")
+        if pov_photons_refraction:
+            tab_write(file, "refraction on\n")
+        if pov_photons_reflection:
+            tab_write(file, "reflection on\n")
+        tab_write(file, "}\n")
 
 
 def write_material(
+    file,
     using_uberpov,
     DEF_MAT_NAME,
     tab_write,
-    safety,
     comments,
     unique_name,
     material_names_dictionary,
-    material
+    material,
 ):
     """Translate Blender material to POV texture{} block and write in exported file."""
     # Assumes only called once on each material
+
+    from .render import safety
+
     if material:
         name_orig = material.name
         name = material_names_dictionary[name_orig] = unique_name(
@@ -122,30 +121,31 @@ def write_material(
     # ref_level_bound=2 Means translation of spec and mir levels for when no map influences them
     # ref_level_bound=3 Means Maximum Spec and Mirror
 
-    def pov_has_no_specular_maps(ref_level_bound):
+    def pov_has_no_specular_maps(file, ref_level_bound):
         """Translate Blender specular map influence to POV finish map trick and write to file."""
         if ref_level_bound == 1:
             if comments:
-                tab_write("//--No specular nor Mirror reflection--\n")
+                tab_write(file, "//--No specular nor Mirror reflection--\n")
             else:
-                tab_write("\n")
-            tab_write("#declare %s = finish {\n" % safety(name, ref_level_bound=1))
+                tab_write(file, "\n")
+            tab_write(file, "#declare %s = finish {\n" % safety(name, ref_level_bound=1))
 
         elif ref_level_bound == 2:
             if comments:
                 tab_write(
-                    "//--translation of spec and mir levels for when no map " "influences them--\n"
+                    file,
+                    "//--translation of spec and mir levels for when no map " "influences them--\n",
                 )
             else:
-                tab_write("\n")
-            tab_write("#declare %s = finish {\n" % safety(name, ref_level_bound=2))
+                tab_write(file, "\n")
+            tab_write(file, "#declare %s = finish {\n" % safety(name, ref_level_bound=2))
 
         elif ref_level_bound == 3:
             if comments:
-                tab_write("//--Maximum Spec and Mirror--\n")
+                tab_write(file, "//--Maximum Spec and Mirror--\n")
             else:
-                tab_write("\n")
-            tab_write("#declare %s = finish {\n" % safety(name, ref_level_bound=3))
+                tab_write(file, "\n")
+            tab_write(file, "#declare %s = finish {\n" % safety(name, ref_level_bound=3))
         if material:
             # POV-Ray 3.7 now uses two diffuse values respectively for front and back shading
             # (the back diffuse is like blender translucency)
@@ -180,188 +180,198 @@ def write_material(
             if material.pov.diffuse_shader == "OREN_NAYAR" and ref_level_bound != 3:
                 # Blender roughness is what is generally called oren nayar Sigma,
                 # and brilliance in POV-Ray.
-                tab_write("brilliance %.3g\n" % (0.9 + material.roughness))
+                tab_write(file, "brilliance %.3g\n" % (0.9 + material.roughness))
 
             if material.pov.diffuse_shader == "TOON" and ref_level_bound != 3:
-                tab_write("brilliance %.3g\n" % (0.01 + material.diffuse_toon_smooth * 0.25))
+                tab_write(file, "brilliance %.3g\n" % (0.01 + material.diffuse_toon_smooth * 0.25))
                 # Lower diffuse and increase specular for toon effect seems to look better
                 # in POV-Ray.
                 front_diffuse *= 0.5
 
             if material.pov.diffuse_shader == "MINNAERT" and ref_level_bound != 3:
-                # tab_write("aoi %.3g\n" % material.darkness)
+                # tab_write(file, "aoi %.3g\n" % material.pov.darkness)
                 pass  # let's keep things simple for now
             if material.pov.diffuse_shader == "FRESNEL" and ref_level_bound != 3:
-                # tab_write("aoi %.3g\n" % material.diffuse_fresnel_factor)
+                # tab_write(file, "aoi %.3g\n" % material.pov.diffuse_fresnel_factor)
                 pass  # let's keep things simple for now
             if material.pov.diffuse_shader == "LAMBERT" and ref_level_bound != 3:
                 # trying to best match lambert attenuation by that constant brilliance value
-                tab_write("brilliance 1\n")
+                tab_write(file, "brilliance 1\n")
 
             if ref_level_bound == 2:
                 # ------------------------------ Specular Shader ------------------------------ #
                 # No difference between phong and cook torrence in blender HaHa!
-                if (
-                    material.pov.specular_shader == "COOKTORR"
-                    or material.pov.specular_shader == "PHONG"
-                ):
-                    tab_write("phong %.3g\n" % material.pov.specular_intensity)
-                    tab_write("phong_size %.3g\n" % (material.pov.specular_hardness / 3.14))
+                if material.pov.specular_shader in ["COOKTORR", "PHONG"]:
+                    tab_write(file, "phong %.3g\n" % material.pov.specular_intensity)
+                    tab_write(file, "phong_size %.3g\n" % (material.pov.specular_hardness / 3.14))
 
                 # POV-Ray 'specular' keyword corresponds to a Blinn model, without the ior.
                 elif material.pov.specular_shader == "BLINN":
                     # Use blender Blinn's IOR just as some factor for spec intensity
                     tab_write(
+                        file,
                         "specular %.3g\n"
-                        % (material.pov.specular_intensity * (material.pov.specular_ior / 4.0))
+                        % (material.pov.specular_intensity * (material.pov.specular_ior / 4.0)),
                     )
-                    tab_write("roughness %.3g\n" % roughness)
+                    tab_write(file, "roughness %.3g\n" % roughness)
                     # Could use brilliance 2(or varying around 2 depending on ior or factor) too.
 
                 elif material.pov.specular_shader == "TOON":
-                    tab_write("phong %.3g\n" % (material.pov.specular_intensity * 2.0))
+                    tab_write(file, "phong %.3g\n" % (material.pov.specular_intensity * 2.0))
                     # use extreme phong_size
-                    tab_write("phong_size %.3g\n" % (0.1 + material.pov.specular_toon_smooth / 2.0))
+                    tab_write(
+                        file, "phong_size %.3g\n" % (0.1 + material.pov.specular_toon_smooth / 2.0)
+                    )
 
                 elif material.pov.specular_shader == "WARDISO":
                     # find best suited default constant for brilliance Use both phong and
                     # specular for some values.
                     tab_write(
+                        file,
                         "specular %.3g\n"
-                        % (material.pov.specular_intensity / (material.pov.specular_slope + 0.0005))
+                        % (
+                            material.pov.specular_intensity / (material.pov.specular_slope + 0.0005)
+                        ),
                     )
                     # find best suited default constant for brilliance Use both phong and
                     # specular for some values.
-                    tab_write("roughness %.4g\n" % (0.0005 + material.pov.specular_slope / 10.0))
+                    tab_write(
+                        file, "roughness %.4g\n" % (0.0005 + material.pov.specular_slope / 10.0)
+                    )
                     # find best suited default constant for brilliance Use both phong and
                     # specular for some values.
-                    tab_write("brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8))
+                    tab_write(file, "brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8))
 
             # -------------------------------------------------------------------------------- #
             elif ref_level_bound == 1:
-                if (
-                    material.pov.specular_shader == "COOKTORR"
-                    or material.pov.specular_shader == "PHONG"
-                ):
-                    tab_write("phong 0\n")  # %.3g\n" % (material.pov.specular_intensity/5))
-                    tab_write("phong_size %.3g\n" % (material.pov.specular_hardness / 3.14))
+                if material.pov.specular_shader in ["COOKTORR", "PHONG"]:
+                    tab_write(file, "phong 0\n")  # %.3g\n" % (material.pov.specular_intensity/5))
+                    tab_write(file, "phong_size %.3g\n" % (material.pov.specular_hardness / 3.14))
 
                 # POV-Ray 'specular' keyword corresponds to a Blinn model, without the ior.
                 elif material.pov.specular_shader == "BLINN":
                     # Use blender Blinn's IOR just as some factor for spec intensity
                     tab_write(
+                        file,
                         "specular %.3g\n"
-                        % (material.pov.specular_intensity * (material.pov.specular_ior / 4.0))
+                        % (material.pov.specular_intensity * (material.pov.specular_ior / 4.0)),
                     )
-                    tab_write("roughness %.3g\n" % roughness)
+                    tab_write(file, "roughness %.3g\n" % roughness)
                     # Could use brilliance 2(or varying around 2 depending on ior or factor) too.
 
                 elif material.pov.specular_shader == "TOON":
-                    tab_write("phong %.3g\n" % (material.pov.specular_intensity * 2.0))
+                    tab_write(file, "phong %.3g\n" % (material.pov.specular_intensity * 2.0))
                     # use extreme phong_size
-                    tab_write("phong_size %.3g\n" % (0.1 + material.pov.specular_toon_smooth / 2.0))
+                    tab_write(
+                        file, "phong_size %.3g\n" % (0.1 + material.pov.specular_toon_smooth / 2.0)
+                    )
 
                 elif material.pov.specular_shader == "WARDISO":
                     # find best suited default constant for brilliance Use both phong and
                     # specular for some values.
                     tab_write(
+                        file,
                         "specular %.3g\n"
-                        % (material.pov.specular_intensity / (material.pov.specular_slope + 0.0005))
+                        % (
+                            material.pov.specular_intensity / (material.pov.specular_slope + 0.0005)
+                        ),
                     )
                     # find best suited default constant for brilliance Use both phong and
                     # specular for some values.
-                    tab_write("roughness %.4g\n" % (0.0005 + material.pov.specular_slope / 10.0))
+                    tab_write(
+                        file, "roughness %.4g\n" % (0.0005 + material.pov.specular_slope / 10.0)
+                    )
                     # find best suited default constant for brilliance Use both phong and
                     # specular for some values.
-                    tab_write("brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8))
+                    tab_write(file, "brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8))
             elif ref_level_bound == 3:
                 # Spec must be Max at ref_level_bound 3 so that white of mixing texture always shows specularity
                 # That's why it's multiplied by 255. maybe replace by texture's brightest pixel value?
                 if material.pov_texture_slots:
                     max_spec_factor = (
-                            material.pov.specular_intensity
-                            * material.pov.specular_color.v
-                            * 255
-                            * slot.specular_factor
+                        material.pov.specular_intensity
+                        * material.pov.specular_color.v
+                        * 255
+                        * slot.specular_factor
                     )
                 else:
                     max_spec_factor = (
-                            material.pov.specular_intensity
-                            * material.pov.specular_color.v
-                            * 255
+                        material.pov.specular_intensity * material.pov.specular_color.v * 255
                     )
-                tab_write("specular %.3g\n" % max_spec_factor)
-                tab_write("roughness %.3g\n" % (1 / material.pov.specular_hardness))
-            tab_write("diffuse %.3g %.3g\n" % (front_diffuse, back_diffuse))
+                tab_write(file, "specular %.3g\n" % max_spec_factor)
+                tab_write(file, "roughness %.3g\n" % (1 / material.pov.specular_hardness))
+            tab_write(file, "diffuse %.3g, %.3g\n" % (front_diffuse, back_diffuse))
 
-            tab_write("ambient %.3g\n" % material.pov.ambient)
+            tab_write(file, "ambient %.3g\n" % material.pov.ambient)
             # POV-Ray blends the global value
-            # tab_write("ambient rgb <%.3g, %.3g, %.3g>\n" % \
+            # tab_write(file, "ambient rgb <%.3g, %.3g, %.3g>\n" % \
             #         tuple([c*material.pov.ambient for c in world.ambient_color]))
-            tab_write("emission %.3g\n" % material.pov.emit)  # New in POV-Ray 3.7
+            tab_write(file, "emission %.3g\n" % material.pov.emit)  # New in POV-Ray 3.7
 
             # POV-Ray just ignores roughness if there's no specular keyword
-            # tab_write("roughness %.3g\n" % roughness)
+            # tab_write(file, "roughness %.3g\n" % roughness)
 
             if material.pov.conserve_energy:
                 # added for more realistic shading. Needs some checking to see if it
                 # really works. --Maurice.
-                tab_write("conserve_energy\n")
+                tab_write(file, "conserve_energy\n")
 
             if colored_specular_found:
-                tab_write("metallic\n")
+                tab_write(file, "metallic\n")
 
             # 'phong 70.0 '
-            if ref_level_bound != 1:
-                if material.pov_raytrace_mirror.use:
-                    raytrace_mirror = material.pov_raytrace_mirror
-                    if raytrace_mirror.reflect_factor:
-                        tab_write("reflection {\n")
-                        tab_write("rgb <%.3g, %.3g, %.3g>\n" % material.pov.mirror_color[:])
-                        if material.pov.mirror_metallic:
-                            tab_write("metallic %.3g\n" % raytrace_mirror.reflect_factor)
-                        # Blurry reflections for UberPOV
-                        if using_uberpov and raytrace_mirror.gloss_factor < 1.0:
-                            # tab_write("#ifdef(unofficial) #if(unofficial = \"patch\") #if(patch(\"upov-reflection-roughness\") > 0)\n")
-                            tab_write(
-                                "roughness %.6f\n" % (0.000001 / raytrace_mirror.gloss_factor)
-                            )
-                            # tab_write("#end #end #end\n") # This and previous comment for backward compatibility, messier pov code
-                        if material.pov.mirror_use_IOR:  # WORKING ?
-                            # Removed from the line below: gives a more physically correct
-                            # material but needs proper IOR. --Maurice
-                            tab_write("fresnel 1 ")
+            if ref_level_bound != 1 and material.pov_raytrace_mirror.use:
+                raytrace_mirror = material.pov_raytrace_mirror
+                if raytrace_mirror.reflect_factor:
+                    tab_write(file, "reflection {\n")
+                    tab_write(file, "rgb <%.3g, %.3g, %.3g>\n" % material.pov.mirror_color[:])
+                    if material.metallic:
+                        tab_write(file, "metallic %.3g\n" % material.metallic)
+                    # Blurry reflections for UberPOV
+                    if using_uberpov and raytrace_mirror.gloss_factor < 1.0:
+                        # tab_write(file, "#ifdef(unofficial) #if(unofficial = \"patch\") #if(patch(\"upov-reflection-roughness\") > 0)\n")
                         tab_write(
-                            "falloff %.3g exponent %.3g} "
-                            % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_factor)
+                            file, "roughness %.6f\n" % (0.000001 / raytrace_mirror.gloss_factor)
                         )
+                        # tab_write(file, "#end #end #end\n") # This and previous comment for backward compatibility, messier pov code
+                    if material.pov.mirror_use_IOR:  # WORKING ?
+                        # Removed from the line below: gives a more physically correct
+                        # material but needs proper IOR. --Maurice
+                        tab_write(file, "fresnel 1 ")
+                    tab_write(
+                        file,
+                        "falloff %.3g exponent %.3g} "
+                        % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_factor),
+                    )
 
             if material.pov_subsurface_scattering.use:
                 subsurface_scattering = material.pov_subsurface_scattering
                 tab_write(
+                    file,
                     "subsurface { translucency <%.3g, %.3g, %.3g> }\n"
                     % (
                         (subsurface_scattering.radius[0]),
                         (subsurface_scattering.radius[1]),
                         (subsurface_scattering.radius[2]),
-                    )
+                    ),
                 )
 
             if material.pov.irid_enable:
                 tab_write(
+                    file,
                     "irid { %.4g thickness %.4g turbulence %.4g }"
                     % (
                         material.pov.irid_amount,
                         material.pov.irid_thickness,
                         material.pov.irid_turbulence,
-                    )
+                    ),
                 )
 
         else:
-            tab_write("diffuse 0.8\n")
-            tab_write("phong 70.0\n")
+            tab_write(file, "diffuse 0.8\n")
+            tab_write(file, "phong 70.0\n")
 
-            # tab_write("specular 0.2\n")
+            # tab_write(file, "specular 0.2\n")
 
         # This is written into the object
         """
@@ -369,16 +379,16 @@ def write_material(
             'interior { ior %.3g} ' % material.raytrace_transparency.ior
         """
 
-        # tab_write("crand 1.0\n") # Sand granyness
-        # tab_write("metallic %.6f\n" % material.spec)
-        # tab_write("phong %.6f\n" % material.spec)
-        # tab_write("phong_size %.6f\n" % material.spec)
-        # tab_write("brilliance %.6f " % (material.pov.specular_hardness/256.0) # Like hardness
+        # tab_write(file, "crand 1.0\n") # Sand granyness
+        # tab_write(file, "metallic %.6f\n" % material.metallic)
+        # tab_write(file, "phong %.6f\n" % material.spec)
+        # tab_write(file, "phong_size %.6f\n" % material.spec)
+        # tab_write(file, "brilliance %.6f " % (material.pov.specular_hardness/256.0) # Like hardness
 
-        tab_write("}\n\n")
+        tab_write(file, "}\n\n")
 
     # ref_level_bound=2 Means translation of spec and mir levels for when no map influences them
-    pov_has_no_specular_maps(ref_level_bound=2)
+    pov_has_no_specular_maps(file, ref_level_bound=2)
 
     if material:
         special_texture_found = False
@@ -411,1330 +421,7 @@ def write_material(
 
         if special_texture_found or colored_specular_found:
             # ref_level_bound=1 Means No specular nor Mirror reflection
-            pov_has_no_specular_maps(ref_level_bound=1)
+            pov_has_no_specular_maps(file, ref_level_bound=1)
 
             # ref_level_bound=3 Means Maximum Spec and Mirror
-            pov_has_no_specular_maps(ref_level_bound=3)
-
-
-def export_pattern(texture):
-    """Translate Blender procedural textures to POV patterns and write to pov file.
-
-    Function Patterns can be used to better access sub components of a pattern like
-    grey values for influence mapping
-    """
-    tex = texture
-    pat = tex.pov
-    pat_name = "PAT_%s" % string_strip_hyphen(bpy.path.clean_name(tex.name))
-    mapping_dif = "translate <%.4g,%.4g,%.4g> scale <%.4g,%.4g,%.4g>" % (
-        pat.tex_mov_x,
-        pat.tex_mov_y,
-        pat.tex_mov_z,
-        1.0 / pat.tex_scale_x,
-        1.0 / pat.tex_scale_y,
-        1.0 / pat.tex_scale_z,
-    )
-    text_strg = ""
-
-    def export_color_ramp(texture):
-        tex = texture
-        pat = tex.pov
-        col_ramp_strg = "color_map {\n"
-        num_color = 0
-        for el in tex.color_ramp.elements:
-            num_color += 1
-            pos = el.position
-            col = el.color
-            col_r, col_g, col_b, col_a = col[0], col[1], col[2], 1 - col[3]
-            if pat.tex_pattern_type not in {"checker", "hexagon", "square", "triangular", "brick"}:
-                col_ramp_strg += "[%.4g color rgbf<%.4g,%.4g,%.4g,%.4g>] \n" % (
-                    pos,
-                    col_r,
-                    col_g,
-                    col_b,
-                    col_a,
-                )
-            if pat.tex_pattern_type in {"brick", "checker"} and num_color < 3:
-                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-            if pat.tex_pattern_type == "hexagon" and num_color < 4:
-                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-            if pat.tex_pattern_type == "square" and num_color < 5:
-                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-            if pat.tex_pattern_type == "triangular" and num_color < 7:
-                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-
-        col_ramp_strg += "} \n"
-        # end color map
-        return col_ramp_strg
-
-    # much work to be done here only defaults translated for now:
-    # pov noise_generator 3 means perlin noise
-    if tex.type not in {"NONE", "IMAGE"} and pat.tex_pattern_type == "emulator":
-        text_strg += "pigment {\n"
-        # ------------------------- EMULATE BLENDER VORONOI TEXTURE ------------------------- #
-        if tex.type == "VORONOI":
-            text_strg += "crackle\n"
-            text_strg += "    offset %.4g\n" % tex.nabla
-            text_strg += "    form <%.4g,%.4g,%.4g>\n" % (tex.weight_1, tex.weight_2, tex.weight_3)
-            if tex.distance_metric == "DISTANCE":
-                text_strg += "    metric 2.5\n"
-            if tex.distance_metric == "DISTANCE_SQUARED":
-                text_strg += "    metric 2.5\n"
-                text_strg += "    poly_wave 2\n"
-            if tex.distance_metric == "MINKOVSKY":
-                text_strg += "    metric %s\n" % tex.minkovsky_exponent
-            if tex.distance_metric == "MINKOVSKY_FOUR":
-                text_strg += "    metric 4\n"
-            if tex.distance_metric == "MINKOVSKY_HALF":
-                text_strg += "    metric 0.5\n"
-            if tex.distance_metric == "CHEBYCHEV":
-                text_strg += "    metric 10\n"
-            if tex.distance_metric == "MANHATTAN":
-                text_strg += "    metric 1\n"
-
-            if tex.color_mode == "POSITION":
-                text_strg += "solid\n"
-            text_strg += "scale 0.25\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0 color rgbt<0,0,0,1>]\n"
-                text_strg += "[1 color rgbt<1,1,1,0>]\n"
-                text_strg += "}\n"
-
-        # ------------------------- EMULATE BLENDER CLOUDS TEXTURE ------------------------- #
-        if tex.type == "CLOUDS":
-            if tex.noise_type == "SOFT_NOISE":
-                text_strg += "wrinkles\n"
-                text_strg += "scale 0.25\n"
-            else:
-                text_strg += "granite\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0 color rgbt<0,0,0,1>]\n"
-                text_strg += "[1 color rgbt<1,1,1,0>]\n"
-                text_strg += "}\n"
-
-        # ------------------------- EMULATE BLENDER WOOD TEXTURE ------------------------- #
-        if tex.type == "WOOD":
-            if tex.wood_type == "RINGS":
-                text_strg += "wood\n"
-                text_strg += "scale 0.25\n"
-            if tex.wood_type == "RINGNOISE":
-                text_strg += "wood\n"
-                text_strg += "scale 0.25\n"
-                text_strg += "turbulence %.4g\n" % (tex.turbulence / 100)
-            if tex.wood_type == "BANDS":
-                text_strg += "marble\n"
-                text_strg += "scale 0.25\n"
-                text_strg += "rotate <45,-45,45>\n"
-            if tex.wood_type == "BANDNOISE":
-                text_strg += "marble\n"
-                text_strg += "scale 0.25\n"
-                text_strg += "rotate <45,-45,45>\n"
-                text_strg += "turbulence %.4g\n" % (tex.turbulence / 10)
-
-            if tex.noise_basis_2 == "SIN":
-                text_strg += "sine_wave\n"
-            if tex.noise_basis_2 == "TRI":
-                text_strg += "triangle_wave\n"
-            if tex.noise_basis_2 == "SAW":
-                text_strg += "ramp_wave\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0 color rgbt<0,0,0,0>]\n"
-                text_strg += "[1 color rgbt<1,1,1,0>]\n"
-                text_strg += "}\n"
-
-        # ------------------------- EMULATE BLENDER STUCCI TEXTURE ------------------------- #
-        if tex.type == "STUCCI":
-            text_strg += "bozo\n"
-            text_strg += "scale 0.25\n"
-            if tex.noise_type == "HARD_NOISE":
-                text_strg += "triangle_wave\n"
-                if tex.use_color_ramp:
-                    text_strg += export_color_ramp(tex)
-                else:
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbf<1,1,1,0>]\n"
-                    text_strg += "[1 color rgbt<0,0,0,1>]\n"
-                    text_strg += "}\n"
-            else:
-                if tex.use_color_ramp:
-                    text_strg += export_color_ramp(tex)
-                else:
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbf<0,0,0,1>]\n"
-                    text_strg += "[1 color rgbt<1,1,1,0>]\n"
-                    text_strg += "}\n"
-
-        # ------------------------- EMULATE BLENDER MAGIC TEXTURE ------------------------- #
-        if tex.type == "MAGIC":
-            text_strg += "leopard\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0 color rgbt<1,1,1,0.5>]\n"
-                text_strg += "[0.25 color rgbf<0,1,0,0.75>]\n"
-                text_strg += "[0.5 color rgbf<0,0,1,0.75>]\n"
-                text_strg += "[0.75 color rgbf<1,0,1,0.75>]\n"
-                text_strg += "[1 color rgbf<0,1,0,0.75>]\n"
-                text_strg += "}\n"
-            text_strg += "scale 0.1\n"
-
-        # ------------------------- EMULATE BLENDER MARBLE TEXTURE ------------------------- #
-        if tex.type == "MARBLE":
-            text_strg += "marble\n"
-            text_strg += "turbulence 0.5\n"
-            text_strg += "noise_generator 3\n"
-            text_strg += "scale 0.75\n"
-            text_strg += "rotate <45,-45,45>\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                if tex.marble_type == "SOFT":
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbt<0,0,0,0>]\n"
-                    text_strg += "[0.05 color rgbt<0,0,0,0>]\n"
-                    text_strg += "[1 color rgbt<0.9,0.9,0.9,0>]\n"
-                    text_strg += "}\n"
-                elif tex.marble_type == "SHARP":
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbt<0,0,0,0>]\n"
-                    text_strg += "[0.025 color rgbt<0,0,0,0>]\n"
-                    text_strg += "[1 color rgbt<0.9,0.9,0.9,0>]\n"
-                    text_strg += "}\n"
-                else:
-                    text_strg += "[0 color rgbt<0,0,0,0>]\n"
-                    text_strg += "[1 color rgbt<1,1,1,0>]\n"
-                    text_strg += "}\n"
-            if tex.noise_basis_2 == "SIN":
-                text_strg += "sine_wave\n"
-            if tex.noise_basis_2 == "TRI":
-                text_strg += "triangle_wave\n"
-            if tex.noise_basis_2 == "SAW":
-                text_strg += "ramp_wave\n"
-
-        # ------------------------- EMULATE BLENDER BLEND TEXTURE ------------------------- #
-        if tex.type == "BLEND":
-            if tex.progression == "RADIAL":
-                text_strg += "radial\n"
-                if tex.use_flip_axis == "HORIZONTAL":
-                    text_strg += "rotate x*90\n"
-                else:
-                    text_strg += "rotate <-90,0,90>\n"
-                text_strg += "ramp_wave\n"
-            elif tex.progression == "SPHERICAL":
-                text_strg += "spherical\n"
-                text_strg += "scale 3\n"
-                text_strg += "poly_wave 1\n"
-            elif tex.progression == "QUADRATIC_SPHERE":
-                text_strg += "spherical\n"
-                text_strg += "scale 3\n"
-                text_strg += "    poly_wave 2\n"
-            elif tex.progression == "DIAGONAL":
-                text_strg += "gradient <1,1,0>\n"
-                text_strg += "scale 3\n"
-            elif tex.use_flip_axis == "HORIZONTAL":
-                text_strg += "gradient x\n"
-                text_strg += "scale 2.01\n"
-            elif tex.use_flip_axis == "VERTICAL":
-                text_strg += "gradient y\n"
-                text_strg += "scale 2.01\n"
-            # text_strg+="ramp_wave\n"
-            # text_strg+="frequency 0.5\n"
-            text_strg += "phase 0.5\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0 color rgbt<1,1,1,0>]\n"
-                text_strg += "[1 color rgbf<0,0,0,1>]\n"
-                text_strg += "}\n"
-            if tex.progression == "LINEAR":
-                text_strg += "    poly_wave 1\n"
-            if tex.progression == "QUADRATIC":
-                text_strg += "    poly_wave 2\n"
-            if tex.progression == "EASING":
-                text_strg += "    poly_wave 1.5\n"
-
-        # ------------------------- EMULATE BLENDER MUSGRAVE TEXTURE ------------------------- #
-        # if tex.type == 'MUSGRAVE':
-        # text_strg+="function{ f_ridged_mf( x, y, 0, 1, 2, 9, -0.5, 3,3 )*0.5}\n"
-        # text_strg+="color_map {\n"
-        # text_strg+="[0 color rgbf<0,0,0,1>]\n"
-        # text_strg+="[1 color rgbf<1,1,1,0>]\n"
-        # text_strg+="}\n"
-        # simplified for now:
-
-        if tex.type == "MUSGRAVE":
-            text_strg += "bozo scale 0.25 \n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += (
-                    "color_map {[0.5 color rgbf<0,0,0,1>][1 color rgbt<1,1,1,0>]}ramp_wave \n"
-                )
-
-        # ------------------------- EMULATE BLENDER DISTORTED NOISE TEXTURE ------------------------- #
-        if tex.type == "DISTORTED_NOISE":
-            text_strg += "average\n"
-            text_strg += "  pigment_map {\n"
-            text_strg += "  [1 bozo scale 0.25 turbulence %.4g\n" % tex.distortion
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0 color rgbt<1,1,1,0>]\n"
-                text_strg += "[1 color rgbf<0,0,0,1>]\n"
-                text_strg += "}\n"
-            text_strg += "]\n"
-
-            if tex.noise_distortion == "CELL_NOISE":
-                text_strg += "  [1 cells scale 0.1\n"
-                if tex.use_color_ramp:
-                    text_strg += export_color_ramp(tex)
-                else:
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
-                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
-                    text_strg += "}\n"
-                text_strg += "]\n"
-            if tex.noise_distortion == "VORONOI_CRACKLE":
-                text_strg += "  [1 crackle scale 0.25\n"
-                if tex.use_color_ramp:
-                    text_strg += export_color_ramp(tex)
-                else:
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
-                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
-                    text_strg += "}\n"
-                text_strg += "]\n"
-            if tex.noise_distortion in [
-                "VORONOI_F1",
-                "VORONOI_F2",
-                "VORONOI_F3",
-                "VORONOI_F4",
-                "VORONOI_F2_F1",
-            ]:
-                text_strg += "  [1 crackle metric 2.5 scale 0.25 turbulence %.4g\n" % (
-                    tex.distortion / 2
-                )
-                if tex.use_color_ramp:
-                    text_strg += export_color_ramp(tex)
-                else:
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
-                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
-                    text_strg += "}\n"
-                text_strg += "]\n"
-            else:
-                text_strg += "  [1 wrinkles scale 0.25\n"
-                if tex.use_color_ramp:
-                    text_strg += export_color_ramp(tex)
-                else:
-                    text_strg += "color_map {\n"
-                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
-                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
-                    text_strg += "}\n"
-                text_strg += "]\n"
-            text_strg += "  }\n"
-
-        # ------------------------- EMULATE BLENDER NOISE TEXTURE ------------------------- #
-        if tex.type == "NOISE":
-            text_strg += "cells\n"
-            text_strg += "turbulence 3\n"
-            text_strg += "omega 3\n"
-            if tex.use_color_ramp:
-                text_strg += export_color_ramp(tex)
-            else:
-                text_strg += "color_map {\n"
-                text_strg += "[0.75 color rgb<0,0,0,>]\n"
-                text_strg += "[1 color rgb<1,1,1,>]\n"
-                text_strg += "}\n"
-
-        # ------------------------- IGNORE OTHER BLENDER TEXTURE ------------------------- #
-        else:  # non translated textures
-            pass
-        text_strg += "}\n\n"
-
-        text_strg += "#declare f%s=\n" % pat_name
-        text_strg += "function{pigment{%s}}\n" % pat_name
-        text_strg += "\n"
-
-    elif pat.tex_pattern_type != "emulator":
-        text_strg += "pigment {\n"
-        text_strg += "%s\n" % pat.tex_pattern_type
-        if pat.tex_pattern_type == "agate":
-            text_strg += "agate_turb %.4g\n" % pat.modifier_turbulence
-        if pat.tex_pattern_type in {"spiral1", "spiral2", "tiling"}:
-            text_strg += "%s\n" % pat.modifier_numbers
-        if pat.tex_pattern_type == "quilted":
-            text_strg += "control0 %s control1 %s\n" % (
-                pat.modifier_control0,
-                pat.modifier_control1,
-            )
-        if pat.tex_pattern_type == "mandel":
-            text_strg += "%s exponent %s \n" % (pat.f_iter, pat.f_exponent)
-        if pat.tex_pattern_type == "julia":
-            text_strg += "<%.4g, %.4g> %s exponent %s \n" % (
-                pat.julia_complex_1,
-                pat.julia_complex_2,
-                pat.f_iter,
-                pat.f_exponent,
-            )
-        if pat.tex_pattern_type == "magnet" and pat.magnet_style == "mandel":
-            text_strg += "%s mandel %s \n" % (pat.magnet_type, pat.f_iter)
-        if pat.tex_pattern_type == "magnet" and pat.magnet_style == "julia":
-            text_strg += "%s julia <%.4g, %.4g> %s\n" % (
-                pat.magnet_type,
-                pat.julia_complex_1,
-                pat.julia_complex_2,
-                pat.f_iter,
-            )
-        if pat.tex_pattern_type in {"mandel", "julia", "magnet"}:
-            text_strg += "interior %s, %.4g\n" % (pat.f_ior, pat.f_ior_fac)
-            text_strg += "exterior %s, %.4g\n" % (pat.f_eor, pat.f_eor_fac)
-        if pat.tex_pattern_type == "gradient":
-            text_strg += "<%s, %s, %s> \n" % (
-                pat.grad_orient_x,
-                pat.grad_orient_y,
-                pat.grad_orient_z,
-            )
-        if pat.tex_pattern_type == "pavement":
-            num_tiles = pat.pave_tiles
-            num_pattern = 1
-            if pat.pave_sides == "4" and pat.pave_tiles == 3:
-                num_pattern = pat.pave_pat_2
-            if pat.pave_sides == "6" and pat.pave_tiles == 3:
-                num_pattern = pat.pave_pat_3
-            if pat.pave_sides == "3" and pat.pave_tiles == 4:
-                num_pattern = pat.pave_pat_3
-            if pat.pave_sides == "3" and pat.pave_tiles == 5:
-                num_pattern = pat.pave_pat_4
-            if pat.pave_sides == "4" and pat.pave_tiles == 4:
-                num_pattern = pat.pave_pat_5
-            if pat.pave_sides == "6" and pat.pave_tiles == 4:
-                num_pattern = pat.pave_pat_7
-            if pat.pave_sides == "4" and pat.pave_tiles == 5:
-                num_pattern = pat.pave_pat_12
-            if pat.pave_sides == "3" and pat.pave_tiles == 6:
-                num_pattern = pat.pave_pat_12
-            if pat.pave_sides == "6" and pat.pave_tiles == 5:
-                num_pattern = pat.pave_pat_22
-            if pat.pave_sides == "4" and pat.pave_tiles == 6:
-                num_pattern = pat.pave_pat_35
-            if pat.pave_sides == "6" and pat.pave_tiles == 6:
-                num_tiles = 5
-            text_strg += "number_of_sides %s number_of_tiles %s pattern %s form %s \n" % (
-                pat.pave_sides,
-                num_tiles,
-                num_pattern,
-                pat.pave_form,
-            )
-        # ------------------------- functions ------------------------- #
-        if pat.tex_pattern_type == "function":
-            text_strg += "{ %s" % pat.func_list
-            text_strg += "(x"
-            if pat.func_plus_x != "NONE":
-                if pat.func_plus_x == "increase":
-                    text_strg += "*"
-                if pat.func_plus_x == "plus":
-                    text_strg += "+"
-                text_strg += "%.4g" % pat.func_x
-            text_strg += ",y"
-            if pat.func_plus_y != "NONE":
-                if pat.func_plus_y == "increase":
-                    text_strg += "*"
-                if pat.func_plus_y == "plus":
-                    text_strg += "+"
-                text_strg += "%.4g" % pat.func_y
-            text_strg += ",z"
-            if pat.func_plus_z != "NONE":
-                if pat.func_plus_z == "increase":
-                    text_strg += "*"
-                if pat.func_plus_z == "plus":
-                    text_strg += "+"
-                text_strg += "%.4g" % pat.func_z
-            sort = -1
-            if pat.func_list in {
-                "f_comma",
-                "f_crossed_trough",
-                "f_cubic_saddle",
-                "f_cushion",
-                "f_devils_curve",
-                "f_enneper",
-                "f_glob",
-                "f_heart",
-                "f_hex_x",
-                "f_hex_y",
-                "f_hunt_surface",
-                "f_klein_bottle",
-                "f_kummer_surface_v1",
-                "f_lemniscate_of_gerono",
-                "f_mitre",
-                "f_nodal_cubic",
-                "f_noise_generator",
-                "f_odd",
-                "f_paraboloid",
-                "f_pillow",
-                "f_piriform",
-                "f_quantum",
-                "f_quartic_paraboloid",
-                "f_quartic_saddle",
-                "f_sphere",
-                "f_steiners_roman",
-                "f_torus_gumdrop",
-                "f_umbrella",
-            }:
-                sort = 0
-            if pat.func_list in {
-                "f_bicorn",
-                "f_bifolia",
-                "f_boy_surface",
-                "f_superellipsoid",
-                "f_torus",
-            }:
-                sort = 1
-            if pat.func_list in {
-                "f_ellipsoid",
-                "f_folium_surface",
-                "f_hyperbolic_torus",
-                "f_kampyle_of_eudoxus",
-                "f_parabolic_torus",
-                "f_quartic_cylinder",
-                "f_torus2",
-            }:
-                sort = 2
-            if pat.func_list in {
-                "f_blob2",
-                "f_cross_ellipsoids",
-                "f_flange_cover",
-                "f_isect_ellipsoids",
-                "f_kummer_surface_v2",
-                "f_ovals_of_cassini",
-                "f_rounded_box",
-                "f_spikes_2d",
-                "f_strophoid",
-            }:
-                sort = 3
-            if pat.func_list in {
-                "f_algbr_cyl1",
-                "f_algbr_cyl2",
-                "f_algbr_cyl3",
-                "f_algbr_cyl4",
-                "f_blob",
-                "f_mesh1",
-                "f_poly4",
-                "f_spikes",
-            }:
-                sort = 4
-            if pat.func_list in {
-                "f_devils_curve_2d",
-                "f_dupin_cyclid",
-                "f_folium_surface_2d",
-                "f_hetero_mf",
-                "f_kampyle_of_eudoxus_2d",
-                "f_lemniscate_of_gerono_2d",
-                "f_polytubes",
-                "f_ridge",
-                "f_ridged_mf",
-                "f_spiral",
-                "f_witch_of_agnesi",
-            }:
-                sort = 5
-            if pat.func_list in {"f_helix1", "f_helix2", "f_piriform_2d", "f_strophoid_2d"}:
-                sort = 6
-            if pat.func_list == "f_helical_torus":
-                sort = 7
-            if sort > -1:
-                text_strg += ",%.4g" % pat.func_P0
-            if sort > 0:
-                text_strg += ",%.4g" % pat.func_P1
-            if sort > 1:
-                text_strg += ",%.4g" % pat.func_P2
-            if sort > 2:
-                text_strg += ",%.4g" % pat.func_P3
-            if sort > 3:
-                text_strg += ",%.4g" % pat.func_P4
-            if sort > 4:
-                text_strg += ",%.4g" % pat.func_P5
-            if sort > 5:
-                text_strg += ",%.4g" % pat.func_P6
-            if sort > 6:
-                text_strg += ",%.4g" % pat.func_P7
-                text_strg += ",%.4g" % pat.func_P8
-                text_strg += ",%.4g" % pat.func_P9
-            text_strg += ")}\n"
-        # ------------------------- end functions ------------------------- #
-        if pat.tex_pattern_type not in {"checker", "hexagon", "square", "triangular", "brick"}:
-            text_strg += "color_map {\n"
-        num_color = 0
-        if tex.use_color_ramp:
-            for el in tex.color_ramp.elements:
-                num_color += 1
-                pos = el.position
-                col = el.color
-                col_r, col_g, col_b, col_a = col[0], col[1], col[2], 1 - col[3]
-                if pat.tex_pattern_type not in {
-                    "checker",
-                    "hexagon",
-                    "square",
-                    "triangular",
-                    "brick",
-                }:
-                    text_strg += "[%.4g color rgbf<%.4g,%.4g,%.4g,%.4g>] \n" % (
-                        pos,
-                        col_r,
-                        col_g,
-                        col_b,
-                        col_a,
-                    )
-                if pat.tex_pattern_type in {"brick", "checker"} and num_color < 3:
-                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-                if pat.tex_pattern_type == "hexagon" and num_color < 4:
-                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-                if pat.tex_pattern_type == "square" and num_color < 5:
-                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-                if pat.tex_pattern_type == "triangular" and num_color < 7:
-                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
-        else:
-            text_strg += "[0 color rgbf<0,0,0,1>]\n"
-            text_strg += "[1 color rgbf<1,1,1,0>]\n"
-        if pat.tex_pattern_type not in {"checker", "hexagon", "square", "triangular", "brick"}:
-            text_strg += "} \n"
-        if pat.tex_pattern_type == "brick":
-            text_strg += "brick_size <%.4g, %.4g, %.4g> mortar %.4g \n" % (
-                pat.brick_size_x,
-                pat.brick_size_y,
-                pat.brick_size_z,
-                pat.brick_mortar,
-            )
-        text_strg += "%s \n" % mapping_dif
-        text_strg += "rotate <%.4g,%.4g,%.4g> \n" % (pat.tex_rot_x, pat.tex_rot_y, pat.tex_rot_z)
-        text_strg += "turbulence <%.4g,%.4g,%.4g> \n" % (
-            pat.warp_turbulence_x,
-            pat.warp_turbulence_y,
-            pat.warp_turbulence_z,
-        )
-        text_strg += "octaves %s \n" % pat.modifier_octaves
-        text_strg += "lambda %.4g \n" % pat.modifier_lambda
-        text_strg += "omega %.4g \n" % pat.modifier_omega
-        text_strg += "frequency %.4g \n" % pat.modifier_frequency
-        text_strg += "phase %.4g \n" % pat.modifier_phase
-        text_strg += "}\n\n"
-        text_strg += "#declare f%s=\n" % pat_name
-        text_strg += "function{pigment{%s}}\n" % pat_name
-        text_strg += "\n"
-    return text_strg
-
-
-def string_strip_hyphen(name):
-    """POV naming schemes like to conform to most restrictive charsets."""
-    return name.replace("-", "")
-
-
-# WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-def write_nodes(pov_mat_name, ntree, file):
-    """Translate Blender node trees to pov and write them to file."""
-    # such function local inlined import are official guidelines
-    # of Blender Foundation to lighten addons footprint at startup
-    from os import path
-
-    declare_nodes = []
-    scene = bpy.context.scene
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayFinishNode" and node.outputs["Finish"].is_linked:
-            file.write("#declare %s = finish {\n" % pov_node_name)
-            emission = node.inputs["Emission"].default_value
-            if node.inputs["Emission"].is_linked:
-                pass
-            file.write("    emission %.4g\n" % emission)
-            for link in ntree.links:
-                if link.to_node == node:
-
-                    if link.from_node.bl_idname == "PovrayDiffuseNode":
-                        intensity = 0
-                        albedo = ""
-                        brilliance = 0
-                        crand = 0
-                        if link.from_node.inputs["Intensity"].is_linked:
-                            pass
-                        else:
-                            intensity = link.from_node.inputs["Intensity"].default_value
-                        if link.from_node.inputs["Albedo"].is_linked:
-                            pass
-                        else:
-                            if link.from_node.inputs["Albedo"].default_value:
-                                albedo = "albedo"
-                        file.write("    diffuse %s %.4g\n" % (albedo, intensity))
-                        if link.from_node.inputs["Brilliance"].is_linked:
-                            pass
-                        else:
-                            brilliance = link.from_node.inputs["Brilliance"].default_value
-                        file.write("    brilliance %.4g\n" % brilliance)
-                        if link.from_node.inputs["Crand"].is_linked:
-                            pass
-                        else:
-                            crand = link.from_node.inputs["Crand"].default_value
-                        if crand > 0:
-                            file.write("    crand %.4g\n" % crand)
-
-                    if link.from_node.bl_idname == "PovraySubsurfaceNode":
-                        if scene.povray.sslt_enable:
-                            energy = 0
-                            r = g = b = 0
-                            if link.from_node.inputs["Translucency"].is_linked:
-                                pass
-                            else:
-                                r, g, b, a = link.from_node.inputs["Translucency"].default_value[:]
-                            if link.from_node.inputs["Energy"].is_linked:
-                                pass
-                            else:
-                                energy = link.from_node.inputs["Energy"].default_value
-                            file.write(
-                                "    subsurface { translucency <%.4g,%.4g,%.4g>*%s }\n"
-                                % (r, g, b, energy)
-                            )
-
-                    if link.from_node.bl_idname in {"PovraySpecularNode", "PovrayPhongNode"}:
-                        intensity = 0
-                        albedo = ""
-                        roughness = 0
-                        metallic = 0
-                        phong_size = 0
-                        highlight = "specular"
-                        if link.from_node.inputs["Intensity"].is_linked:
-                            pass
-                        else:
-                            intensity = link.from_node.inputs["Intensity"].default_value
-
-                        if link.from_node.inputs["Albedo"].is_linked:
-                            pass
-                        else:
-                            if link.from_node.inputs["Albedo"].default_value:
-                                albedo = "albedo"
-                        if link.from_node.bl_idname in {"PovrayPhongNode"}:
-                            highlight = "phong"
-                        file.write("    %s %s %.4g\n" % (highlight, albedo, intensity))
-
-                        if link.from_node.bl_idname in {"PovraySpecularNode"}:
-                            if link.from_node.inputs["Roughness"].is_linked:
-                                pass
-                            else:
-                                roughness = link.from_node.inputs["Roughness"].default_value
-                            file.write("    roughness %.6g\n" % roughness)
-
-                        if link.from_node.bl_idname in {"PovrayPhongNode"}:
-                            if link.from_node.inputs["Size"].is_linked:
-                                pass
-                            else:
-                                phong_size = link.from_node.inputs["Size"].default_value
-                            file.write("    phong_size %s\n" % phong_size)
-
-                        if link.from_node.inputs["Metallic"].is_linked:
-                            pass
-                        else:
-                            metallic = link.from_node.inputs["Metallic"].default_value
-                        file.write("    metallic %.4g\n" % metallic)
-
-                    if link.from_node.bl_idname in {"PovrayMirrorNode"}:
-                        file.write("    reflection {\n")
-                        color = None
-                        exponent = 0
-                        metallic = 0
-                        falloff = 0
-                        fresnel = ""
-                        conserve = ""
-                        if link.from_node.inputs["Color"].is_linked:
-                            pass
-                        else:
-                            color = link.from_node.inputs["Color"].default_value[:]
-                        file.write(
-                            "    <%.4g,%.4g,%.4g>\n"
-                            % (color[0], color[1], color[2])
-                        )
-
-                        if link.from_node.inputs["Exponent"].is_linked:
-                            pass
-                        else:
-                            exponent = link.from_node.inputs["Exponent"].default_value
-                        file.write("    exponent %.4g\n" % exponent)
-
-                        if link.from_node.inputs["Falloff"].is_linked:
-                            pass
-                        else:
-                            falloff = link.from_node.inputs["Falloff"].default_value
-                        file.write("    falloff %.4g\n" % falloff)
-
-                        if link.from_node.inputs["Metallic"].is_linked:
-                            pass
-                        else:
-                            metallic = link.from_node.inputs["Metallic"].default_value
-                        file.write("    metallic %.4g" % metallic)
-
-                        if link.from_node.inputs["Fresnel"].is_linked:
-                            pass
-                        else:
-                            if link.from_node.inputs["Fresnel"].default_value:
-                                fresnel = "fresnel"
-
-                        if link.from_node.inputs["Conserve energy"].is_linked:
-                            pass
-                        else:
-                            if link.from_node.inputs["Conserve energy"].default_value:
-                                conserve = "conserve_energy"
-
-                        file.write("    %s}\n    %s\n" % (fresnel, conserve))
-
-                    if link.from_node.bl_idname == "PovrayAmbientNode":
-                        ambient = (0, 0, 0)
-                        if link.from_node.inputs["Ambient"].is_linked:
-                            pass
-                        else:
-                            ambient = link.from_node.inputs["Ambient"].default_value[:]
-                        file.write("    ambient <%.4g,%.4g,%.4g>\n" % ambient)
-
-                    if link.from_node.bl_idname in {"PovrayIridescenceNode"}:
-                        file.write("    irid {\n")
-                        amount = 0
-                        thickness = 0
-                        turbulence = 0
-                        if link.from_node.inputs["Amount"].is_linked:
-                            pass
-                        else:
-                            amount = link.from_node.inputs["Amount"].default_value
-                        file.write("    %.4g\n" % amount)
-
-                        if link.from_node.inputs["Thickness"].is_linked:
-                            pass
-                        else:
-                            exponent = link.from_node.inputs["Thickness"].default_value
-                        file.write("    thickness %.4g\n" % thickness)
-
-                        if link.from_node.inputs["Turbulence"].is_linked:
-                            pass
-                        else:
-                            falloff = link.from_node.inputs["Turbulence"].default_value
-                        file.write("    turbulence %.4g}\n" % turbulence)
-
-            file.write("}\n")
-
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayTransformNode" and node.outputs["Transform"].is_linked:
-            tx = node.inputs["Translate x"].default_value
-            ty = node.inputs["Translate y"].default_value
-            tz = node.inputs["Translate z"].default_value
-            rx = node.inputs["Rotate x"].default_value
-            ry = node.inputs["Rotate y"].default_value
-            rz = node.inputs["Rotate z"].default_value
-            sx = node.inputs["Scale x"].default_value
-            sy = node.inputs["Scale y"].default_value
-            sz = node.inputs["Scale z"].default_value
-            file.write(
-                "#declare %s = transform {\n"
-                "    translate<%.4g,%.4g,%.4g>\n"
-                "    rotate<%.4g,%.4g,%.4g>\n"
-                "    scale<%.4g,%.4g,%.4g>}\n" % (pov_node_name, tx, ty, tz, rx, ry, rz, sx, sy, sz)
-            )
-
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayColorImageNode" and node.outputs["Pigment"].is_linked:
-            declare_nodes.append(node.name)
-            if node.image == "":
-                file.write("#declare %s = pigment { color rgb 0.8}\n" % pov_node_name)
-            else:
-                im = bpy.data.images[node.image]
-                if im.filepath and path.exists(bpy.path.abspath(im.filepath)):  # (os.path)
-                    transform = ""
-                    for link in ntree.links:
-                        if (
-                            link.from_node.bl_idname == "PovrayTransformNode"
-                            and link.to_node == node
-                        ):
-                            pov_trans_name = (
-                                string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
-                                + "_%s" % pov_mat_name
-                            )
-                            transform = "transform {%s}" % pov_trans_name
-                    uv = ""
-                    if node.map_type == "uv_mapping":
-                        uv = "uv_mapping"
-                    filepath = bpy.path.abspath(im.filepath)
-                    file.write("#declare %s = pigment {%s image_map {\n" % (pov_node_name, uv))
-                    premul = "off"
-                    if node.premultiplied:
-                        premul = "on"
-                    once = ""
-                    if node.once:
-                        once = "once"
-                    file.write(
-                        '    "%s"\n    gamma %.6g\n    premultiplied %s\n'
-                        % (filepath, node.inputs["Gamma"].default_value, premul)
-                    )
-                    file.write("    %s\n" % once)
-                    if node.map_type != "uv_mapping":
-                        file.write("    map_type %s\n" % node.map_type)
-                    file.write(
-                        "    interpolate %s\n    filter all %.4g\n    transmit all %.4g\n"
-                        % (
-                            node.interpolate,
-                            node.inputs["Filter"].default_value,
-                            node.inputs["Transmit"].default_value,
-                        )
-                    )
-                    file.write("    }\n")
-                    file.write("    %s\n" % transform)
-                    file.write("    }\n")
-
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayImagePatternNode" and node.outputs["Pattern"].is_linked:
-            declare_nodes.append(node.name)
-            if node.image != "":
-                im = bpy.data.images[node.image]
-                if im.filepath and path.exists(bpy.path.abspath(im.filepath)):
-                    transform = ""
-                    for link in ntree.links:
-                        if (
-                            link.from_node.bl_idname == "PovrayTransformNode"
-                            and link.to_node == node
-                        ):
-                            pov_trans_name = (
-                                string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
-                                + "_%s" % pov_mat_name
-                            )
-                            transform = "transform {%s}" % pov_trans_name
-                    uv = ""
-                    if node.map_type == "uv_mapping":
-                        uv = "uv_mapping"
-                    filepath = bpy.path.abspath(im.filepath)
-                    file.write("#macro %s() %s image_pattern {\n" % (pov_node_name, uv))
-                    premul = "off"
-                    if node.premultiplied:
-                        premul = "on"
-                    once = ""
-                    if node.once:
-                        once = "once"
-                    file.write(
-                        '    "%s"\n    gamma %.6g\n    premultiplied %s\n'
-                        % (filepath, node.inputs["Gamma"].default_value, premul)
-                    )
-                    file.write("    %s\n" % once)
-                    if node.map_type != "uv_mapping":
-                        file.write("    map_type %s\n" % node.map_type)
-                    file.write("    interpolate %s\n" % node.interpolate)
-                    file.write("    }\n")
-                    file.write("    %s\n" % transform)
-                    file.write("#end\n")
-
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayBumpMapNode" and node.outputs["Normal"].is_linked:
-            if node.image != "":
-                im = bpy.data.images[node.image]
-                if im.filepath and path.exists(bpy.path.abspath(im.filepath)):
-                    transform = ""
-                    for link in ntree.links:
-                        if (
-                            link.from_node.bl_idname == "PovrayTransformNode"
-                            and link.to_node == node
-                        ):
-                            pov_trans_name = (
-                                string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
-                                + "_%s" % pov_mat_name
-                            )
-                            transform = "transform {%s}" % pov_trans_name
-                    uv = ""
-                    if node.map_type == "uv_mapping":
-                        uv = "uv_mapping"
-                    filepath = bpy.path.abspath(im.filepath)
-                    file.write("#declare %s = normal {%s bump_map {\n" % (pov_node_name, uv))
-                    once = ""
-                    if node.once:
-                        once = "once"
-                    file.write('    "%s"\n' % filepath)
-                    file.write("    %s\n" % once)
-                    if node.map_type != "uv_mapping":
-                        file.write("    map_type %s\n" % node.map_type)
-                    bump_size = node.inputs["Normal"].default_value
-                    if node.inputs["Normal"].is_linked:
-                        pass
-                    file.write(
-                        "    interpolate %s\n    bump_size %.4g\n" % (node.interpolate, bump_size)
-                    )
-                    file.write("    }\n")
-                    file.write("    %s\n" % transform)
-                    file.write("    }\n")
-                    declare_nodes.append(node.name)
-
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayPigmentNode" and node.outputs["Pigment"].is_linked:
-            declare_nodes.append(node.name)
-            r, g, b = node.inputs["Color"].default_value[:]
-            f = node.inputs["Filter"].default_value
-            t = node.inputs["Transmit"].default_value
-            if node.inputs["Color"].is_linked:
-                pass
-            file.write(
-                "#declare %s = pigment{color srgbft <%.4g,%.4g,%.4g,%.4g,%.4g>}\n"
-                % (pov_node_name, r, g, b, f, t)
-            )
-
-    for node in ntree.nodes:
-        pov_node_name = string_strip_hyphen(bpy.path.clean_name(node.name)) + "_%s" % pov_mat_name
-        if node.bl_idname == "PovrayTextureNode" and node.outputs["Texture"].is_linked:
-            declare_nodes.append(node.name)
-            r, g, b = node.inputs["Pigment"].default_value[:]
-            pov_col_name = "color rgb <%.4g,%.4g,%.4g>" % (r, g, b)
-            if node.inputs["Pigment"].is_linked:
-                for link in ntree.links:
-                    if link.to_node == node and link.to_socket.name == "Pigment":
-                        pov_col_name = (
-                            string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
-                            + "_%s" % pov_mat_name
-                        )
-            file.write("#declare %s = texture{\n    pigment{%s}\n" % (pov_node_name, pov_col_name))
-            if node.inputs["Normal"].is_linked:
-                for link in ntree.links:
-                    if (
-                        link.to_node == node
-                        and link.to_socket.name == "Normal"
-                        and link.from_node.name in declare_nodes
-                    ):
-                        pov_nor_name = (
-                            string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
-                            + "_%s" % pov_mat_name
-                        )
-                        file.write("    normal{%s}\n" % pov_nor_name)
-            if node.inputs["Finish"].is_linked:
-                for link in ntree.links:
-                    if link.to_node == node and link.to_socket.name == "Finish":
-                        pov_fin_name = (
-                            string_strip_hyphen(bpy.path.clean_name(link.from_node.name))
-                            + "_%s" % pov_mat_name
-                        )
-                        file.write("    finish{%s}\n" % pov_fin_name)
-            file.write("}\n")
-            declare_nodes.append(node.name)
-
-    for i in range(0, len(ntree.nodes)):
-        for node in ntree.nodes:
-            if node.bl_idname in {"ShaderNodeGroup", "ShaderTextureMapNode"}:
-                for output in node.outputs:
-                    if (
-                        output.name == "Texture"
-                        and output.is_linked
-                        and (node.name not in declare_nodes)
-                    ):
-                        declare = True
-                        for link in ntree.links:
-                            if link.to_node == node and link.to_socket.name not in {
-                                "",
-                                "Color ramp",
-                                "Mapping",
-                                "Transform",
-                                "Modifier",
-                            }:
-                                if link.from_node.name not in declare_nodes:
-                                    declare = False
-                        if declare:
-                            pov_node_name = (
-                                string_strip_hyphen(bpy.path.clean_name(node.name))
-                                + "_%s" % pov_mat_name
-                            )
-                            uv = ""
-                            warp = ""
-                            for link in ntree.links:
-                                if (
-                                    link.to_node == node
-                                    and link.from_node.bl_idname == "PovrayMappingNode"
-                                    and link.from_node.warp_type != "NONE"
-                                ):
-                                    w_type = link.from_node.warp_type
-                                    if w_type == "uv_mapping":
-                                        uv = "uv_mapping"
-                                    else:
-                                        tor = ""
-                                        if w_type == "toroidal":
-                                            tor = (
-                                                "major_radius %.4g"
-                                                % link.from_node.warp_tor_major_radius
-                                            )
-                                        orient = link.from_node.warp_orientation
-                                        exp = link.from_node.warp_dist_exp
-                                        warp = "warp{%s orientation %s dist_exp %.4g %s}" % (
-                                            w_type,
-                                            orient,
-                                            exp,
-                                            tor,
-                                        )
-                                        if link.from_node.warp_type == "planar":
-                                            warp = "warp{%s %s %.4g}" % (w_type, orient, exp)
-                                        if link.from_node.warp_type == "cubic":
-                                            warp = "warp{%s}" % w_type
-                            file.write("#declare %s = texture {%s\n" % (pov_node_name, uv))
-                            pattern = node.inputs[0].default_value
-                            advanced = ""
-                            if node.inputs[0].is_linked:
-                                for link in ntree.links:
-                                    if (
-                                        link.to_node == node
-                                        and link.from_node.bl_idname == "ShaderPatternNode"
-                                    ):
-                                        # ------------ advanced ------------------------- #
-                                        lfn = link.from_node
-                                        pattern = lfn.pattern
-                                        if pattern == "agate":
-                                            advanced = "agate_turb %.4g" % lfn.agate_turb
-                                        if pattern == "crackle":
-                                            advanced = "form <%.4g,%.4g,%.4g>" % (
-                                                lfn.crackle_form_x,
-                                                lfn.crackle_form_y,
-                                                lfn.crackle_form_z,
-                                            )
-                                            advanced += " metric %.4g" % lfn.crackle_metric
-                                            if lfn.crackle_solid:
-                                                advanced += " solid"
-                                        if pattern in {"spiral1", "spiral2"}:
-                                            advanced = "%.4g" % lfn.spiral_arms
-                                        if pattern in {"tiling"}:
-                                            advanced = "%.4g" % lfn.tiling_number
-                                        if pattern in {"gradient"}:
-                                            advanced = "%s" % lfn.gradient_orient
-                                    if (
-                                        link.to_node == node
-                                        and link.from_node.bl_idname == "PovrayImagePatternNode"
-                                    ):
-                                        pov_macro_name = (
-                                            string_strip_hyphen(
-                                                bpy.path.clean_name(link.from_node.name)
-                                            )
-                                            + "_%s" % pov_mat_name
-                                        )
-                                        pattern = "%s()" % pov_macro_name
-                            file.write("    %s %s %s\n" % (pattern, advanced, warp))
-
-                            repeat = ""
-                            for link in ntree.links:
-                                if (
-                                    link.to_node == node
-                                    and link.from_node.bl_idname == "PovrayMultiplyNode"
-                                ):
-                                    if link.from_node.amount_x > 1:
-                                        repeat += "warp{repeat %.4g * x}" % link.from_node.amount_x
-                                    if link.from_node.amount_y > 1:
-                                        repeat += " warp{repeat %.4g * y}" % link.from_node.amount_y
-                                    if link.from_node.amount_z > 1:
-                                        repeat += " warp{repeat %.4g * z}" % link.from_node.amount_z
-
-                            transform = ""
-                            for link in ntree.links:
-                                if (
-                                    link.to_node == node
-                                    and link.from_node.bl_idname == "PovrayTransformNode"
-                                ):
-                                    pov_trans_name = (
-                                        string_strip_hyphen(
-                                            bpy.path.clean_name(link.from_node.name)
-                                        )
-                                        + "_%s" % pov_mat_name
-                                    )
-                                    transform = "transform {%s}" % pov_trans_name
-                            x = 0
-                            y = 0
-                            z = 0
-                            d = 0
-                            e = 0
-                            f = 0
-                            g = 0
-                            h = 0
-                            modifier = False
-                            for link in ntree.links:
-                                if (
-                                    link.to_node == node
-                                    and link.from_node.bl_idname == "PovrayModifierNode"
-                                ):
-                                    modifier = True
-                                    if link.from_node.inputs["Turb X"].is_linked:
-                                        pass
-                                    else:
-                                        x = link.from_node.inputs["Turb X"].default_value
-
-                                    if link.from_node.inputs["Turb Y"].is_linked:
-                                        pass
-                                    else:
-                                        y = link.from_node.inputs["Turb Y"].default_value
-
-                                    if link.from_node.inputs["Turb Z"].is_linked:
-                                        pass
-                                    else:
-                                        z = link.from_node.inputs["Turb Z"].default_value
-
-                                    if link.from_node.inputs["Octaves"].is_linked:
-                                        pass
-                                    else:
-                                        d = link.from_node.inputs["Octaves"].default_value
-
-                                    if link.from_node.inputs["Lambda"].is_linked:
-                                        pass
-                                    else:
-                                        e = link.from_node.inputs["Lambda"].default_value
-
-                                    if link.from_node.inputs["Omega"].is_linked:
-                                        pass
-                                    else:
-                                        f = link.from_node.inputs["Omega"].default_value
-
-                                    if link.from_node.inputs["Frequency"].is_linked:
-                                        pass
-                                    else:
-                                        g = link.from_node.inputs["Frequency"].default_value
-
-                                    if link.from_node.inputs["Phase"].is_linked:
-                                        pass
-                                    else:
-                                        h = link.from_node.inputs["Phase"].default_value
-
-                            turb = "turbulence <%.4g,%.4g,%.4g>" % (x, y, z)
-                            octv = "octaves %s" % d
-                            lmbd = "lambda %.4g" % e
-                            omg = "omega %.4g" % f
-                            freq = "frequency %.4g" % g
-                            pha = "phase %.4g" % h
-
-                            file.write("\n")
-                            if pattern not in {
-                                "checker",
-                                "hexagon",
-                                "square",
-                                "triangular",
-                                "brick",
-                            }:
-                                file.write("    texture_map {\n")
-                            if node.inputs["Color ramp"].is_linked:
-                                for link in ntree.links:
-                                    if (
-                                        link.to_node == node
-                                        and link.from_node.bl_idname == "ShaderNodeValToRGB"
-                                    ):
-                                        els = link.from_node.color_ramp.elements
-                                        n = -1
-                                        for el in els:
-                                            n += 1
-                                            pov_in_mat_name = string_strip_hyphen(
-                                                bpy.path.clean_name(link.from_node.name)
-                                            ) + "_%s_%s" % (n, pov_mat_name)
-                                            default = True
-                                            for ilink in ntree.links:
-                                                if (
-                                                    ilink.to_node == node
-                                                    and ilink.to_socket.name == str(n)
-                                                ):
-                                                    default = False
-                                                    pov_in_mat_name = (
-                                                        string_strip_hyphen(
-                                                            bpy.path.clean_name(
-                                                                ilink.from_node.name
-                                                            )
-                                                        )
-                                                        + "_%s" % pov_mat_name
-                                                    )
-                                            if default:
-                                                r, g, b, a = el.color[:]
-                                                file.write(
-                                                    "    #declare %s = texture{"
-                                                    "pigment{"
-                                                    "color srgbt <%.4g,%.4g,%.4g,%.4g>}};\n"
-                                                    % (pov_in_mat_name, r, g, b, 1 - a)
-                                                )
-                                            file.write(
-                                                "    [%s %s]\n" % (el.position, pov_in_mat_name)
-                                            )
-                            else:
-                                els = [[0, 0, 0, 0], [1, 1, 1, 1]]
-                                for t in range(0, 2):
-                                    pov_in_mat_name = string_strip_hyphen(
-                                        bpy.path.clean_name(link.from_node.name)
-                                    ) + "_%s_%s" % (t, pov_mat_name)
-                                    default = True
-                                    for ilink in ntree.links:
-                                        if ilink.to_node == node and ilink.to_socket.name == str(t):
-                                            default = False
-                                            pov_in_mat_name = (
-                                                string_strip_hyphen(
-                                                    bpy.path.clean_name(ilink.from_node.name)
-                                                )
-                                                + "_%s" % pov_mat_name
-                                            )
-                                    if default:
-                                        r, g, b = els[t][1], els[t][2], els[t][3]
-                                        if pattern not in {
-                                            "checker",
-                                            "hexagon",
-                                            "square",
-                                            "triangular",
-                                            "brick",
-                                        }:
-                                            file.write(
-                                                "    #declare %s = texture{pigment{color rgb <%.4g,%.4g,%.4g>}};\n"
-                                                % (pov_in_mat_name, r, g, b)
-                                            )
-                                        else:
-                                            file.write(
-                                                "    texture{pigment{color rgb <%.4g,%.4g,%.4g>}}\n"
-                                                % (r, g, b)
-                                            )
-                                    if pattern not in {
-                                        "checker",
-                                        "hexagon",
-                                        "square",
-                                        "triangular",
-                                        "brick",
-                                    }:
-                                        file.write("    [%s %s]\n" % (els[t][0], pov_in_mat_name))
-                                    else:
-                                        if not default:
-                                            file.write("    texture{%s}\n" % pov_in_mat_name)
-                            if pattern not in {
-                                "checker",
-                                "hexagon",
-                                "square",
-                                "triangular",
-                                "brick",
-                            }:
-                                file.write("}\n")
-                            if pattern == "brick":
-                                file.write(
-                                    "brick_size <%.4g, %.4g, %.4g> mortar %.4g \n"
-                                    % (
-                                        node.brick_size_x,
-                                        node.brick_size_y,
-                                        node.brick_size_z,
-                                        node.brick_mortar,
-                                    )
-                                )
-                            file.write("    %s %s" % (repeat, transform))
-                            if modifier:
-                                file.write(
-                                    " %s %s %s %s %s %s" % (turb, octv, lmbd, omg, freq, pha)
-                                )
-                            file.write("}\n")
-                            declare_nodes.append(node.name)
-
-    for link in ntree.links:
-        if link.to_node.bl_idname == "PovrayOutputNode" and link.from_node.name in declare_nodes:
-            pov_mat_node_name = (
-                string_strip_hyphen(bpy.path.clean_name(link.from_node.name)) + "_%s" % pov_mat_name
-            )
-            file.write("#declare %s = %s\n" % (pov_mat_name, pov_mat_node_name))
+            pov_has_no_specular_maps(file, ref_level_bound=3)
diff --git a/render_povray/shading_gui.py b/render_povray/shading_gui.py
index 0eb9e5f0c..55e411e30 100755
--- a/render_povray/shading_gui.py
+++ b/render_povray/shading_gui.py
@@ -14,7 +14,7 @@ from bl_ui import properties_material
 for member in dir(properties_material):
     subclass = getattr(properties_material, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_material
 
 from .shading_properties import check_material
@@ -28,10 +28,10 @@ def simple_material(mat):
 class MaterialButtonsPanel:
     """Use this class to define buttons from the material tab of properties window."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "material"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -42,20 +42,24 @@ class MaterialButtonsPanel:
 
 class MATERIAL_PT_POV_shading(MaterialButtonsPanel, Panel):
     bl_label = "Shading"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         mat = context.material
         engine = context.scene.render.engine
-        return check_material(mat) and (mat.pov.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES)
+        return (
+            check_material(mat)
+            and (mat.pov.type in {"SURFACE", "WIRE"})
+            and (engine in cls.COMPAT_ENGINES)
+        )
 
     def draw(self, context):
         layout = self.layout
 
         mat = context.material  # FORMERLY : #active_node_mat(context.material)
 
-        if mat.pov.type in {'SURFACE', 'WIRE'}:
+        if mat.pov.type in {"SURFACE", "WIRE"}:
             split = layout.split()
 
             col = split.column()
@@ -107,8 +111,8 @@ class MATERIAL_PT_POV_sss(MaterialButtonsPanel, Panel):
     """Use this class to define pov sss buttons panel."""
 
     bl_label = "Subsurface Scattering"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -116,7 +120,7 @@ class MATERIAL_PT_POV_sss(MaterialButtonsPanel, Panel):
         engine = context.scene.render.engine
         return (
             check_material(mat)
-            and (mat.pov.type in {'SURFACE', 'WIRE'})
+            and (mat.pov.type in {"SURFACE", "WIRE"})
             and (engine in cls.COMPAT_ENGINES)
         )
 
@@ -138,9 +142,9 @@ class MATERIAL_PT_POV_sss(MaterialButtonsPanel, Panel):
         row = layout.row().split()
         sub = row.row(align=True).split(align=True, factor=0.75)
         sub.menu(MATERIAL_MT_POV_sss_presets.__name__, text=MATERIAL_MT_POV_sss_presets.bl_label)
-        sub.operator(MATERIAL_OT_POV_sss_add_preset.bl_idname, text="", icon='ADD')
+        sub.operator(MATERIAL_OT_POV_sss_add_preset.bl_idname, text="", icon="ADD")
         sub.operator(
-            MATERIAL_OT_POV_sss_add_preset.bl_idname, text="", icon='REMOVE'
+            MATERIAL_OT_POV_sss_add_preset.bl_idname, text="", icon="REMOVE"
         ).remove_active = True
 
         split = layout.split()
@@ -168,8 +172,8 @@ class MATERIAL_PT_POV_activate_node(MaterialButtonsPanel, Panel):
 
     bl_label = "Activate Node Settings"
     bl_context = "material"
-    bl_options = {'HIDE_HEADER'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"HIDE_HEADER"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -188,7 +192,7 @@ class MATERIAL_PT_POV_activate_node(MaterialButtonsPanel, Panel):
         # layout.operator("pov.material_use_nodes", icon='SOUND')#'NODETREE')
         # the above replaced with a context hook below:
         layout.operator(
-            "WM_OT_context_toggle", text="Use POV-Ray Nodes", icon='NODETREE'
+            "WM_OT_context_toggle", text="Use POV-Ray Nodes", icon="NODETREE"
         ).data_path = "material.pov.material_use_nodes"
 
 
@@ -197,8 +201,8 @@ class MATERIAL_PT_POV_active_node(MaterialButtonsPanel, Panel):
 
     bl_label = "Active Node Settings"
     bl_context = "material"
-    bl_options = {'HIDE_HEADER'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"HIDE_HEADER"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -216,20 +220,16 @@ class MATERIAL_PT_POV_active_node(MaterialButtonsPanel, Panel):
         node_tree = mat.node_tree
         if node_tree and mat.use_nodes:
             layout = self.layout
-            node = node_tree.nodes.active
-            if node:
+            if node := node_tree.nodes.active:
                 layout.prop(mat.pov, "material_active_node")
                 layout.context_pointer_set("node", node)
                 if hasattr(node, "draw_buttons_ext"):
                     node.draw_buttons_ext(context, layout)
                 elif hasattr(node, "draw_buttons"):
                     node.draw_buttons(context, layout)
-                value_inputs = [
-                    socket
-                    for socket in node.inputs
-                    if socket.enabled and not socket.is_linked
-                ]
-                if value_inputs:
+                if value_inputs := [
+                    socket for socket in node.inputs if socket.enabled and not socket.is_linked
+                ]:
                     layout.separator()
                     layout.label(text="Inputs:")
                     for socket in value_inputs:
@@ -243,7 +243,7 @@ class MATERIAL_PT_POV_specular(MaterialButtonsPanel, Panel):
     """Use this class to define standard material specularity (highlights) buttons."""
 
     bl_label = "Specular"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -251,16 +251,16 @@ class MATERIAL_PT_POV_specular(MaterialButtonsPanel, Panel):
         engine = context.scene.render.engine
         return (
             check_material(mat)
-            and (mat.pov.type in {'SURFACE', 'WIRE'})
+            and (mat.pov.type in {"SURFACE", "WIRE"})
             and (engine in cls.COMPAT_ENGINES)
         )
 
     def draw(self, context):
         layout = self.layout
 
-        mat = context.material.pov
+        mat = context.material
 
-        layout.active = not mat.use_shadeless
+        layout.active = not mat.pov.use_shadeless
 
         split = layout.split()
 
@@ -269,26 +269,26 @@ class MATERIAL_PT_POV_specular(MaterialButtonsPanel, Panel):
         col.prop(mat, "specular_intensity", text="Intensity")
 
         col = split.column()
-        col.prop(mat, "specular_shader", text="")
-        col.prop(mat, "use_specular_ramp", text="Ramp")
+        col.prop(mat.pov, "specular_shader", text="")
+        col.prop(mat.pov, "use_specular_ramp", text="Ramp")
 
         col = layout.column()
-        if mat.specular_shader in {'COOKTORR', 'PHONG'}:
-            col.prop(mat, "specular_hardness", text="Hardness")
-        elif mat.specular_shader == 'BLINN':
+        if mat.pov.specular_shader in {"COOKTORR", "PHONG"}:
+            col.prop(mat.pov, "specular_hardness", text="Hardness")
+        elif mat.pov.specular_shader == "BLINN":
             row = col.row()
-            row.prop(mat, "specular_hardness", text="Hardness")
-            row.prop(mat, "specular_ior", text="IOR")
-        elif mat.specular_shader == 'WARDISO':
-            col.prop(mat, "specular_slope", text="Slope")
-        elif mat.specular_shader == 'TOON':
+            row.prop(mat.pov, "specular_hardness", text="Hardness")
+            row.prop(mat.pov, "specular_ior", text="IOR")
+        elif mat.pov.specular_shader == "WARDISO":
+            col.prop(mat.pov, "specular_slope", text="Slope")
+        elif mat.pov.specular_shader == "TOON":
             row = col.row()
-            row.prop(mat, "specular_toon_size", text="Size")
-            row.prop(mat, "specular_toon_smooth", text="Smooth")
+            row.prop(mat.pov, "specular_toon_size", text="Size")
+            row.prop(mat.pov, "specular_toon_smooth", text="Smooth")
 
-        if mat.use_specular_ramp:
+        if mat.pov.use_specular_ramp:
             layout.separator()
-            layout.template_color_ramp(mat, "specular_ramp", expand=True)
+            layout.template_color_ramp(mat.pov, "specular_ramp", expand=True)
             layout.separator()
 
             row = layout.row()
@@ -302,9 +302,9 @@ class MATERIAL_PT_POV_mirror(MaterialButtonsPanel, Panel):
     """Use this class to define standard material reflectivity (mirror) buttons."""
 
     bl_label = "Mirror"
-    bl_options = {'DEFAULT_CLOSED'}
+    bl_options = {"DEFAULT_CLOSED"}
     bl_idname = "MATERIAL_PT_POV_raytrace_mirror"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -312,7 +312,7 @@ class MATERIAL_PT_POV_mirror(MaterialButtonsPanel, Panel):
         engine = context.scene.render.engine
         return (
             check_material(mat)
-            and (mat.pov.type in {'SURFACE', 'WIRE'})
+            and (mat.pov.type in {"SURFACE", "WIRE"})
             and (engine in cls.COMPAT_ENGINES)
         )
 
@@ -368,7 +368,7 @@ class MATERIAL_PT_POV_transp(MaterialButtonsPanel, Panel):
     """Use this class to define pov material transparency (alpha) buttons."""
 
     bl_label = "Transparency"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -376,7 +376,7 @@ class MATERIAL_PT_POV_transp(MaterialButtonsPanel, Panel):
         engine = context.scene.render.engine
         return (
             check_material(mat)
-            and (mat.pov.type in {'SURFACE', 'WIRE'})
+            and (mat.pov.type in {"SURFACE", "WIRE"})
             and (engine in cls.COMPAT_ENGINES)
         )
 
@@ -404,7 +404,7 @@ class MATERIAL_PT_POV_transp(MaterialButtonsPanel, Panel):
         col = split.column()
         col.prop(mat.pov, "alpha")
         row = col.row()
-        row.active = (base_mat.pov.transparency_method != 'MASK') and (not mat.pov.use_shadeless)
+        row.active = (base_mat.pov.transparency_method != "MASK") and (not mat.pov.use_shadeless)
         row.prop(mat.pov, "specular_alpha", text="Specular")
 
         col = split.column()
@@ -414,7 +414,7 @@ class MATERIAL_PT_POV_transp(MaterialButtonsPanel, Panel):
         sub.active = rayt.fresnel > 0.0
         sub.prop(rayt, "fresnel_factor", text="Blend")
 
-        if base_mat.pov.transparency_method == 'RAYTRACE':
+        if base_mat.pov.transparency_method == "RAYTRACE":
             layout.separator()
             split = layout.split()
             split.active = base_mat.pov.use_transparency
@@ -440,7 +440,7 @@ class MATERIAL_PT_POV_reflection(MaterialButtonsPanel, Panel):
 
     bl_label = "POV-Ray Reflection"
     bl_parent_id = "MATERIAL_PT_POV_raytrace_mirror"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -472,13 +472,13 @@ class MATERIAL_PT_POV_reflection(MaterialButtonsPanel, Panel):
         col2.active = mat.pov_raytrace_mirror.use
         col2.prop(mat.pov, "mirror_use_IOR")
         if mat.pov.mirror_use_IOR:
-            col2.alignment = 'CENTER'
+            col2.alignment = "CENTER"
             col2.label(text="The current Raytrace ")
             col2.label(text="Transparency IOR is: " + str(mat.pov_raytrace_transparency.ior))
-        col2.prop(mat.pov, "mirror_metallic")
 
 
-'''
+
+"""
 #group some native Blender (SSS) and POV (Fade)settings under such a parent panel?
 class MATERIAL_PT_POV_interior(MaterialButtonsPanel, Panel):
     bl_label = "POV-Ray Interior"
@@ -496,14 +496,14 @@ class MATERIAL_PT_POV_interior(MaterialButtonsPanel, Panel):
 
     def draw_header(self, context):
         mat = context.material
-'''
+"""
 
 
 class MATERIAL_PT_POV_fade_color(MaterialButtonsPanel, Panel):
     """Use this class to define pov fading (absorption) color buttons."""
 
     bl_label = "POV-Ray Absorption"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
     # bl_parent_id = "material.pov_interior"
 
     @classmethod
@@ -537,7 +537,7 @@ class MATERIAL_PT_POV_caustics(MaterialButtonsPanel, Panel):
     """Use this class to define pov caustics buttons."""
 
     bl_label = "Caustics"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -580,7 +580,7 @@ class MATERIAL_PT_POV_caustics(MaterialButtonsPanel, Panel):
 
             if not mat.pov.refraction_caustics and not mat.pov.photons_reflection:
                 col = layout.column()
-                col.alignment = 'CENTER'
+                col.alignment = "CENTER"
                 col.label(text="Caustics override is on, ")
                 col.label(text="but you didn't chose any !")
 
@@ -589,15 +589,15 @@ class MATERIAL_PT_strand(MaterialButtonsPanel, Panel):
     """Use this class to define Blender strand antialiasing buttons."""
 
     bl_label = "Strand"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
         mat = context.material
         engine = context.scene.render.engine
         return (
-            mat and (mat.pov.type in {'SURFACE', 'WIRE', 'HALO'}) and (engine in cls.COMPAT_ENGINES)
+            mat and (mat.pov.type in {"SURFACE", "WIRE", "HALO"}) and (engine in cls.COMPAT_ENGINES)
         )
 
     def draw(self, context):
@@ -624,7 +624,7 @@ class MATERIAL_PT_strand(MaterialButtonsPanel, Panel):
         col.label(text="Shading:")
         col.prop(tan, "width_fade")
         ob = context.object
-        if ob and ob.type == 'MESH':
+        if ob and ob.type == "MESH":
             col.prop_search(tan, "uv_layer", ob.data, "uv_layers", text="")
         else:
             col.prop(tan, "uv_layer", text="")
@@ -640,16 +640,16 @@ class MATERIAL_PT_POV_replacement_text(MaterialButtonsPanel, Panel):
     """Use this class to define pov custom code declared name field."""
 
     bl_label = "Custom POV Code"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
         mat = context.material
         col = layout.column()
-        col.alignment = 'RIGHT'
+        col.alignment = "RIGHT"
         col.label(text="Override properties with this")
         col.label(text="text editor {pov code} block")
-        layout.prop(mat.pov, "replacement_text", text="#declare name", icon='SYNTAX_ON')
+        layout.prop(mat.pov, "replacement_text", text="#declare name", icon="SYNTAX_ON")
 
 
 classes = (
diff --git a/render_povray/shading_nodes.py b/render_povray/shading_nodes.py
deleted file mode 100755
index fef5d3813..000000000
--- a/render_povray/shading_nodes.py
+++ /dev/null
@@ -1,1992 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-# <pep8 compliant>
-""""Nodes based User interface for shaders exported to POV textures."""
-import bpy
-
-from bpy.utils import register_class, unregister_class
-from bpy.types import Menu, Node, NodeSocket, CompositorNodeTree, TextureNodeTree, Operator
-from bpy.props import (
-    StringProperty,
-    BoolProperty,
-    IntProperty,
-    FloatProperty,
-    FloatVectorProperty,
-    EnumProperty,
-)
-import nodeitems_utils
-from nodeitems_utils import NodeCategory, NodeItem
-
-
-# ---------------------------------------------------------------- #
-# Pov Nodes init
-# ---------------------------------------------------------------- #
-
-
-class PovraySocketUniversal(NodeSocket):
-    bl_idname = "PovraySocketUniversal"
-    bl_label = "Povray Socket"
-    value_unlimited: bpy.props.FloatProperty(default=0.0)
-    value_0_1: bpy.props.FloatProperty(min=0.0, max=1.0, default=0.0)
-    value_0_10: bpy.props.FloatProperty(min=0.0, max=10.0, default=0.0)
-    value_000001_10: bpy.props.FloatProperty(min=0.000001, max=10.0, default=0.0)
-    value_1_9: bpy.props.IntProperty(min=1, max=9, default=1)
-    value_0_255: bpy.props.IntProperty(min=0, max=255, default=0)
-    percent: bpy.props.FloatProperty(min=0.0, max=100.0, default=0.0)
-
-    def draw(self, context, layout, node, text):
-        space = context.space_data
-        tree = space.edit_tree
-        links = tree.links
-        if self.is_linked:
-            value = []
-            for link in links:
-                if link.from_node == node:
-                    inps = link.to_node.inputs
-                    for inp in inps:
-                        if inp.bl_idname == "PovraySocketFloat_0_1" and inp.is_linked:
-                            prop = "value_0_1"
-                            if prop not in value:
-                                value.append(prop)
-                        if inp.bl_idname == "PovraySocketFloat_000001_10" and inp.is_linked:
-                            prop = "value_000001_10"
-                            if prop not in value:
-                                value.append(prop)
-                        if inp.bl_idname == "PovraySocketFloat_0_10" and inp.is_linked:
-                            prop = "value_0_10"
-                            if prop not in value:
-                                value.append(prop)
-                        if inp.bl_idname == "PovraySocketInt_1_9" and inp.is_linked:
-                            prop = "value_1_9"
-                            if prop not in value:
-                                value.append(prop)
-                        if inp.bl_idname == "PovraySocketInt_0_255" and inp.is_linked:
-                            prop = "value_0_255"
-                            if prop not in value:
-                                value.append(prop)
-                        if inp.bl_idname == "PovraySocketFloatUnlimited" and inp.is_linked:
-                            prop = "value_unlimited"
-                            if prop not in value:
-                                value.append(prop)
-            if len(value) == 1:
-                layout.prop(self, "%s" % value[0], text=text)
-            else:
-                layout.prop(self, "percent", text="Percent")
-        else:
-            layout.prop(self, "percent", text=text)
-
-    def draw_color(self, context, node):
-        return (1, 0, 0, 1)
-
-
-class PovraySocketFloat_0_1(NodeSocket):
-    bl_idname = "PovraySocketFloat_0_1"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(
-        description="Input node Value_0_1", min=0, max=1, default=0
-    )
-
-    def draw(self, context, layout, node, text):
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text, slider=True)
-
-    def draw_color(self, context, node):
-        return (0.5, 0.7, 0.7, 1)
-
-
-class PovraySocketFloat_0_10(NodeSocket):
-    bl_idname = "PovraySocketFloat_0_10"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(
-        description="Input node Value_0_10", min=0, max=10, default=0
-    )
-
-    def draw(self, context, layout, node, text):
-        if node.bl_idname == "ShaderNormalMapNode" and node.inputs[2].is_linked:
-            layout.label(text="")
-            self.hide_value = True
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text, slider=True)
-
-    def draw_color(self, context, node):
-        return (0.65, 0.65, 0.65, 1)
-
-
-class PovraySocketFloat_10(NodeSocket):
-    bl_idname = "PovraySocketFloat_10"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(
-        description="Input node Value_10", min=-10, max=10, default=0
-    )
-
-    def draw(self, context, layout, node, text):
-        if node.bl_idname == "ShaderNormalMapNode" and node.inputs[2].is_linked:
-            layout.label(text="")
-            self.hide_value = True
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text, slider=True)
-
-    def draw_color(self, context, node):
-        return (0.65, 0.65, 0.65, 1)
-
-
-class PovraySocketFloatPositive(NodeSocket):
-    bl_idname = "PovraySocketFloatPositive"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(
-        description="Input Node Value Positive", min=0.0, default=0
-    )
-
-    def draw(self, context, layout, node, text):
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text, slider=True)
-
-    def draw_color(self, context, node):
-        return (0.045, 0.005, 0.136, 1)
-
-
-class PovraySocketFloat_000001_10(NodeSocket):
-    bl_idname = "PovraySocketFloat_000001_10"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(min=0.000001, max=10, default=0.000001)
-
-    def draw(self, context, layout, node, text):
-        if self.is_output or self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text, slider=True)
-
-    def draw_color(self, context, node):
-        return (1, 0, 0, 1)
-
-
-class PovraySocketFloatUnlimited(NodeSocket):
-    bl_idname = "PovraySocketFloatUnlimited"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(default=0.0)
-
-    def draw(self, context, layout, node, text):
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text, slider=True)
-
-    def draw_color(self, context, node):
-        return (0.7, 0.7, 1, 1)
-
-
-class PovraySocketInt_1_9(NodeSocket):
-    bl_idname = "PovraySocketInt_1_9"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.IntProperty(
-        description="Input node Value_1_9", min=1, max=9, default=6
-    )
-
-    def draw(self, context, layout, node, text):
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text)
-
-    def draw_color(self, context, node):
-        return (1, 0.7, 0.7, 1)
-
-
-class PovraySocketInt_0_256(NodeSocket):
-    bl_idname = "PovraySocketInt_0_256"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.IntProperty(min=0, max=255, default=0)
-
-    def draw(self, context, layout, node, text):
-        if self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text)
-
-    def draw_color(self, context, node):
-        return (0.5, 0.5, 0.5, 1)
-
-
-class PovraySocketPattern(NodeSocket):
-    bl_idname = "PovraySocketPattern"
-    bl_label = "Povray Socket"
-
-    default_value: bpy.props.EnumProperty(
-        name="Pattern",
-        description="Select the pattern",
-        items=(
-            ("boxed", "Boxed", ""),
-            ("brick", "Brick", ""),
-            ("cells", "Cells", ""),
-            ("checker", "Checker", ""),
-            ("granite", "Granite", ""),
-            ("leopard", "Leopard", ""),
-            ("marble", "Marble", ""),
-            ("onion", "Onion", ""),
-            ("planar", "Planar", ""),
-            ("quilted", "Quilted", ""),
-            ("ripples", "Ripples", ""),
-            ("radial", "Radial", ""),
-            ("spherical", "Spherical", ""),
-            ("spotted", "Spotted", ""),
-            ("waves", "Waves", ""),
-            ("wood", "Wood", ""),
-            ("wrinkles", "Wrinkles", ""),
-        ),
-        default="granite",
-    )
-
-    def draw(self, context, layout, node, text):
-        if self.is_output or self.is_linked:
-            layout.label(text="Pattern")
-        else:
-            layout.prop(self, "default_value", text=text)
-
-    def draw_color(self, context, node):
-        return (1, 1, 1, 1)
-
-
-class PovraySocketColor(NodeSocket):
-    bl_idname = "PovraySocketColor"
-    bl_label = "Povray Socket"
-
-    default_value: FloatVectorProperty(
-        precision=4,
-        step=0.01,
-        min=0,
-        soft_max=1,
-        default=(0.0, 0.0, 0.0),
-        options={"ANIMATABLE"},
-        subtype="COLOR",
-    )
-
-    def draw(self, context, layout, node, text):
-        if self.is_output or self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text)
-
-    def draw_color(self, context, node):
-        return (1, 1, 0, 1)
-
-
-class PovraySocketColorRGBFT(NodeSocket):
-    bl_idname = "PovraySocketColorRGBFT"
-    bl_label = "Povray Socket"
-
-    default_value: FloatVectorProperty(
-        precision=4,
-        step=0.01,
-        min=0,
-        soft_max=1,
-        default=(0.0, 0.0, 0.0),
-        options={"ANIMATABLE"},
-        subtype="COLOR",
-    )
-    f: bpy.props.FloatProperty(default=0.0, min=0.0, max=1.0)
-    t: bpy.props.FloatProperty(default=0.0, min=0.0, max=1.0)
-
-    def draw(self, context, layout, node, text):
-        if self.is_output or self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text=text)
-
-    def draw_color(self, context, node):
-        return (1, 1, 0, 1)
-
-
-class PovraySocketTexture(NodeSocket):
-    bl_idname = "PovraySocketTexture"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.IntProperty()
-
-    def draw(self, context, layout, node, text):
-        layout.label(text=text)
-
-    def draw_color(self, context, node):
-        return (0, 1, 0, 1)
-
-
-class PovraySocketTransform(NodeSocket):
-    bl_idname = "PovraySocketTransform"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.IntProperty(min=0, max=255, default=0)
-
-    def draw(self, context, layout, node, text):
-        layout.label(text=text)
-
-    def draw_color(self, context, node):
-        return (99 / 255, 99 / 255, 199 / 255, 1)
-
-
-class PovraySocketNormal(NodeSocket):
-    bl_idname = "PovraySocketNormal"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.IntProperty(min=0, max=255, default=0)
-
-    def draw(self, context, layout, node, text):
-        layout.label(text=text)
-
-    def draw_color(self, context, node):
-        return (0.65, 0.65, 0.65, 1)
-
-
-class PovraySocketSlope(NodeSocket):
-    bl_idname = "PovraySocketSlope"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.FloatProperty(min=0.0, max=1.0)
-    height: bpy.props.FloatProperty(min=0.0, max=10.0)
-    slope: bpy.props.FloatProperty(min=-10.0, max=10.0)
-
-    def draw(self, context, layout, node, text):
-        if self.is_output or self.is_linked:
-            layout.label(text=text)
-        else:
-            layout.prop(self, "default_value", text="")
-            layout.prop(self, "height", text="")
-            layout.prop(self, "slope", text="")
-
-    def draw_color(self, context, node):
-        return (0, 0, 0, 1)
-
-
-class PovraySocketMap(NodeSocket):
-    bl_idname = "PovraySocketMap"
-    bl_label = "Povray Socket"
-    default_value: bpy.props.StringProperty()
-
-    def draw(self, context, layout, node, text):
-        layout.label(text=text)
-
-    def draw_color(self, context, node):
-        return (0.2, 0, 0.2, 1)
-
-
-class PovrayShaderNodeCategory(NodeCategory):
-    @classmethod
-    def poll(cls, context):
-        return context.space_data.tree_type == "ObjectNodeTree"
-
-
-class PovrayTextureNodeCategory(NodeCategory):
-    @classmethod
-    def poll(cls, context):
-        return context.space_data.tree_type == "TextureNodeTree"
-
-
-class PovraySceneNodeCategory(NodeCategory):
-    @classmethod
-    def poll(cls, context):
-        return context.space_data.tree_type == "CompositorNodeTree"
-
-
-node_categories = [
-    PovrayShaderNodeCategory("SHADEROUTPUT", "Output", items=[NodeItem("PovrayOutputNode")]),
-    PovrayShaderNodeCategory("SIMPLE", "Simple texture", items=[NodeItem("PovrayTextureNode")]),
-    PovrayShaderNodeCategory(
-        "MAPS",
-        "Maps",
-        items=[
-            NodeItem("PovrayBumpMapNode"),
-            NodeItem("PovrayColorImageNode"),
-            NodeItem("ShaderNormalMapNode"),
-            NodeItem("PovraySlopeNode"),
-            NodeItem("ShaderTextureMapNode"),
-            NodeItem("ShaderNodeValToRGB"),
-        ],
-    ),
-    PovrayShaderNodeCategory(
-        "OTHER",
-        "Other patterns",
-        items=[NodeItem("PovrayImagePatternNode"), NodeItem("ShaderPatternNode")],
-    ),
-    PovrayShaderNodeCategory("COLOR", "Color", items=[NodeItem("PovrayPigmentNode")]),
-    PovrayShaderNodeCategory(
-        "TRANSFORM",
-        "Transform",
-        items=[
-            NodeItem("PovrayMappingNode"),
-            NodeItem("PovrayMultiplyNode"),
-            NodeItem("PovrayModifierNode"),
-            NodeItem("PovrayTransformNode"),
-            NodeItem("PovrayValueNode"),
-        ],
-    ),
-    PovrayShaderNodeCategory(
-        "FINISH",
-        "Finish",
-        items=[
-            NodeItem("PovrayFinishNode"),
-            NodeItem("PovrayDiffuseNode"),
-            NodeItem("PovraySpecularNode"),
-            NodeItem("PovrayPhongNode"),
-            NodeItem("PovrayAmbientNode"),
-            NodeItem("PovrayMirrorNode"),
-            NodeItem("PovrayIridescenceNode"),
-            NodeItem("PovraySubsurfaceNode"),
-        ],
-    ),
-    PovrayShaderNodeCategory(
-        "CYCLES",
-        "Cycles",
-        items=[
-            NodeItem("ShaderNodeAddShader"),
-            NodeItem("ShaderNodeAmbientOcclusion"),
-            NodeItem("ShaderNodeAttribute"),
-            NodeItem("ShaderNodeBackground"),
-            NodeItem("ShaderNodeBlackbody"),
-            NodeItem("ShaderNodeBrightContrast"),
-            NodeItem("ShaderNodeBsdfAnisotropic"),
-            NodeItem("ShaderNodeBsdfDiffuse"),
-            NodeItem("ShaderNodeBsdfGlass"),
-            NodeItem("ShaderNodeBsdfGlossy"),
-            NodeItem("ShaderNodeBsdfHair"),
-            NodeItem("ShaderNodeBsdfRefraction"),
-            NodeItem("ShaderNodeBsdfToon"),
-            NodeItem("ShaderNodeBsdfTranslucent"),
-            NodeItem("ShaderNodeBsdfTransparent"),
-            NodeItem("ShaderNodeBsdfVelvet"),
-            NodeItem("ShaderNodeBump"),
-            NodeItem("ShaderNodeCameraData"),
-            NodeItem("ShaderNodeCombineHSV"),
-            NodeItem("ShaderNodeCombineRGB"),
-            NodeItem("ShaderNodeCombineXYZ"),
-            NodeItem("ShaderNodeEmission"),
-            NodeItem("ShaderNodeExtendedMaterial"),
-            NodeItem("ShaderNodeFresnel"),
-            NodeItem("ShaderNodeGamma"),
-            NodeItem("ShaderNodeGeometry"),
-            NodeItem("ShaderNodeGroup"),
-            NodeItem("ShaderNodeHairInfo"),
-            NodeItem("ShaderNodeHoldout"),
-            NodeItem("ShaderNodeHueSaturation"),
-            NodeItem("ShaderNodeInvert"),
-            NodeItem("ShaderNodeLampData"),
-            NodeItem("ShaderNodeLayerWeight"),
-            NodeItem("ShaderNodeLightFalloff"),
-            NodeItem("ShaderNodeLightPath"),
-            NodeItem("ShaderNodeMapping"),
-            NodeItem("ShaderNodeMaterial"),
-            NodeItem("ShaderNodeMath"),
-            NodeItem("ShaderNodeMixRGB"),
-            NodeItem("ShaderNodeMixShader"),
-            NodeItem("ShaderNodeNewGeometry"),
-            NodeItem("ShaderNodeNormal"),
-            NodeItem("ShaderNodeNormalMap"),
-            NodeItem("ShaderNodeObjectInfo"),
-            NodeItem("ShaderNodeOutput"),
-            NodeItem("ShaderNodeOutputLamp"),
-            NodeItem("ShaderNodeOutputLineStyle"),
-            NodeItem("ShaderNodeOutputMaterial"),
-            NodeItem("ShaderNodeOutputWorld"),
-            NodeItem("ShaderNodeParticleInfo"),
-            NodeItem("ShaderNodeRGB"),
-            NodeItem("ShaderNodeRGBCurve"),
-            NodeItem("ShaderNodeRGBToBW"),
-            NodeItem("ShaderNodeScript"),
-            NodeItem("ShaderNodeSeparateHSV"),
-            NodeItem("ShaderNodeSeparateRGB"),
-            NodeItem("ShaderNodeSeparateXYZ"),
-            NodeItem("ShaderNodeSqueeze"),
-            NodeItem("ShaderNodeSubsurfaceScattering"),
-            NodeItem("ShaderNodeTangent"),
-            NodeItem("ShaderNodeTexBrick"),
-            NodeItem("ShaderNodeTexChecker"),
-            NodeItem("ShaderNodeTexCoord"),
-            NodeItem("ShaderNodeTexEnvironment"),
-            NodeItem("ShaderNodeTexGradient"),
-            NodeItem("ShaderNodeTexImage"),
-            NodeItem("ShaderNodeTexMagic"),
-            NodeItem("ShaderNodeTexMusgrave"),
-            NodeItem("ShaderNodeTexNoise"),
-            NodeItem("ShaderNodeTexPointDensity"),
-            NodeItem("ShaderNodeTexSky"),
-            NodeItem("ShaderNodeTexVoronoi"),
-            NodeItem("ShaderNodeTexWave"),
-            NodeItem("ShaderNodeTexture"),
-            NodeItem("ShaderNodeUVAlongStroke"),
-            NodeItem("ShaderNodeUVMap"),
-            NodeItem("ShaderNodeValToRGB"),
-            NodeItem("ShaderNodeValue"),
-            NodeItem("ShaderNodeVectorCurve"),
-            NodeItem("ShaderNodeVectorMath"),
-            NodeItem("ShaderNodeVectorTransform"),
-            NodeItem("ShaderNodeVolumeAbsorption"),
-            NodeItem("ShaderNodeVolumeScatter"),
-            NodeItem("ShaderNodeWavelength"),
-            NodeItem("ShaderNodeWireframe"),
-        ],
-    ),
-    PovrayTextureNodeCategory(
-        "TEXTUREOUTPUT",
-        "Output",
-        items=[NodeItem("TextureNodeValToRGB"), NodeItem("TextureOutputNode")],
-    ),
-    PovraySceneNodeCategory("ISOSURFACE", "Isosurface", items=[NodeItem("IsoPropsNode")]),
-    PovraySceneNodeCategory("FOG", "Fog", items=[NodeItem("PovrayFogNode")]),
-]
-# -------- end nodes init
-# -------- nodes ui
-# -------- Nodes
-
-# def find_node_input(node, name):
-# for input in node.inputs:
-# if input.name == name:
-# return input
-
-# def panel_node_draw(layout, id_data, output_type, input_name):
-# if not id_data.use_nodes:
-# #layout.operator("pov.material_use_nodes", icon='SOUND')#'NODETREE')
-# #layout.operator("pov.use_shading_nodes", icon='NODETREE')
-# layout.operator("WM_OT_context_toggle", icon='NODETREE').data_path = \
-# "material.pov.material_use_nodes"
-# return False
-
-# ntree = id_data.node_tree
-
-# node = find_node(id_data, output_type)
-# if not node:
-# layout.label(text="No output node")
-# else:
-# input = find_node_input(node, input_name)
-# layout.template_node_view(ntree, node, input)
-
-# return True
-
-
-class NODE_MT_POV_map_create(Menu):
-    """Create maps"""
-
-    bl_idname = "POVRAY_MT_node_map_create"
-    bl_label = "Create map"
-
-    def draw(self, context):
-        layout = self.layout
-        layout.operator("node.map_create")
-
-
-def menu_func_nodes(self, context):
-    ob = context.object
-    if hasattr(ob, 'active_material'):
-        mat = context.object.active_material
-        if mat and context.space_data.tree_type == 'ObjectNodeTree':
-            self.layout.prop(mat.pov, "material_use_nodes")
-            self.layout.menu(NODE_MT_POV_map_create.bl_idname)
-            self.layout.operator("wm.updatepreviewkey")
-        if hasattr(mat, 'active_texture') and context.scene.render.engine == 'POVRAY_RENDER':
-            tex = mat.active_texture
-            if tex and context.space_data.tree_type == 'TextureNodeTree':
-                self.layout.prop(tex.pov, "texture_use_nodes")
-
-
-# -------- object
-
-
-class ObjectNodeTree(bpy.types.NodeTree):
-    """Povray Material Nodes"""
-
-    bl_idname = 'ObjectNodeTree'
-    bl_label = 'Povray Object Nodes'
-    bl_icon = 'PLUGIN'
-
-    @classmethod
-    def poll(cls, context):
-        return context.scene.render.engine == 'POVRAY_RENDER'
-
-    @classmethod
-    def get_from_context(cls, context):
-        ob = context.active_object
-        if ob and ob.type not in {'LIGHT'}:
-            ma = ob.active_material
-            if ma is not None:
-                nt_name = ma.node_tree
-                if nt_name != '':
-                    return nt_name, ma, ma
-        return (None, None, None)
-
-    def update(self):
-        self.refresh = True
-
-
-# -------- output # ---------------------------------------------------------------- #
-
-
-class PovrayOutputNode(Node, ObjectNodeTree):
-    """Output"""
-
-    bl_idname = 'PovrayOutputNode'
-    bl_label = 'Output'
-    bl_icon = 'SHADING_TEXTURE'
-
-    def init(self, context):
-
-        self.inputs.new('PovraySocketTexture', "Texture")
-
-    def draw_buttons(self, context, layout):
-
-        ob = context.object
-        layout.prop(ob.pov, "object_ior", slider=True)
-
-    def draw_buttons_ext(self, context, layout):
-
-        ob = context.object
-        layout.prop(ob.pov, "object_ior", slider=True)
-
-    def draw_label(self):
-        return "Output"
-
-
-# -------- material # ---------------------------------------------------------------- #
-class PovrayTextureNode(Node, ObjectNodeTree):
-    """Texture"""
-
-    bl_idname = 'PovrayTextureNode'
-    bl_label = 'Simple texture'
-    bl_icon = 'SHADING_TEXTURE'
-
-    def init(self, context):
-
-        color = self.inputs.new('PovraySocketColor', "Pigment")
-        color.default_value = (1, 1, 1)
-        normal = self.inputs.new('NodeSocketFloat', "Normal")
-        normal.hide_value = True
-        finish = self.inputs.new('NodeSocketVector', "Finish")
-        finish.hide_value = True
-
-        self.outputs.new('PovraySocketTexture', "Texture")
-
-    def draw_label(self):
-        return "Simple texture"
-
-
-class PovrayFinishNode(Node, ObjectNodeTree):
-    """Finish"""
-
-    bl_idname = 'PovrayFinishNode'
-    bl_label = 'Finish'
-    bl_icon = 'SHADING_TEXTURE'
-
-    def init(self, context):
-
-        self.inputs.new('PovraySocketFloat_0_1', "Emission")
-        ambient = self.inputs.new('NodeSocketVector', "Ambient")
-        ambient.hide_value = True
-        diffuse = self.inputs.new('NodeSocketVector', "Diffuse")
-        diffuse.hide_value = True
-        specular = self.inputs.new('NodeSocketVector', "Highlight")
-        specular.hide_value = True
-        mirror = self.inputs.new('NodeSocketVector', "Mirror")
-        mirror.hide_value = True
-        iridescence = self.inputs.new('NodeSocketVector', "Iridescence")
-        iridescence.hide_value = True
-        subsurface = self.inputs.new('NodeSocketVector', "Translucency")
-        subsurface.hide_value = True
-        self.outputs.new('NodeSocketVector', "Finish")
-
-    def draw_label(self):
-        return "Finish"
-
-
-class PovrayDiffuseNode(Node, ObjectNodeTree):
-    """Diffuse"""
-
-    bl_idname = 'PovrayDiffuseNode'
-    bl_label = 'Diffuse'
-    bl_icon = 'MATSPHERE'
-
-    def init(self, context):
-
-        intensity = self.inputs.new('PovraySocketFloat_0_1', "Intensity")
-        intensity.default_value = 0.8
-        albedo = self.inputs.new('NodeSocketBool', "Albedo")
-        albedo.default_value = False
-        brilliance = self.inputs.new('PovraySocketFloat_0_10', "Brilliance")
-        brilliance.default_value = 1.8
-        self.inputs.new('PovraySocketFloat_0_1', "Crand")
-        self.outputs.new('NodeSocketVector', "Diffuse")
-
-    def draw_label(self):
-        return "Diffuse"
-
-
-class PovrayPhongNode(Node, ObjectNodeTree):
-    """Phong"""
-
-    bl_idname = 'PovrayPhongNode'
-    bl_label = 'Phong'
-    bl_icon = 'MESH_UVSPHERE'
-
-    def init(self, context):
-
-        albedo = self.inputs.new('NodeSocketBool', "Albedo")
-        intensity = self.inputs.new('PovraySocketFloat_0_1', "Intensity")
-        intensity.default_value = 0.8
-        phong_size = self.inputs.new('PovraySocketInt_0_256', "Size")
-        phong_size.default_value = 60
-        metallic = self.inputs.new('PovraySocketFloat_0_1', "Metallic")
-
-        self.outputs.new('NodeSocketVector', "Phong")
-
-    def draw_label(self):
-        return "Phong"
-
-
-class PovraySpecularNode(Node, ObjectNodeTree):
-    """Specular"""
-
-    bl_idname = 'PovraySpecularNode'
-    bl_label = 'Specular'
-    bl_icon = 'MESH_UVSPHERE'
-
-    def init(self, context):
-
-        albedo = self.inputs.new('NodeSocketBool', "Albedo")
-        intensity = self.inputs.new('PovraySocketFloat_0_1', "Intensity")
-        intensity.default_value = 0.8
-        roughness = self.inputs.new('PovraySocketFloat_0_1', "Roughness")
-        roughness.default_value = 0.02
-        metallic = self.inputs.new('PovraySocketFloat_0_1', "Metallic")
-
-        self.outputs.new('NodeSocketVector', "Specular")
-
-    def draw_label(self):
-        return "Specular"
-
-
-class PovrayMirrorNode(Node, ObjectNodeTree):
-    """Mirror"""
-
-    bl_idname = 'PovrayMirrorNode'
-    bl_label = 'Mirror'
-    bl_icon = 'SHADING_TEXTURE'
-
-    def init(self, context):
-
-        color = self.inputs.new('PovraySocketColor', "Color")
-        color.default_value = (1, 1, 1)
-        metallic = self.inputs.new('PovraySocketFloat_0_1', "Metallic")
-        metallic.default_value = 1.0
-        exponent = self.inputs.new('PovraySocketFloat_0_1', "Exponent")
-        exponent.default_value = 1.0
-        self.inputs.new('PovraySocketFloat_0_1', "Falloff")
-        self.inputs.new('NodeSocketBool', "Fresnel")
-        self.inputs.new('NodeSocketBool', "Conserve energy")
-        self.outputs.new('NodeSocketVector', "Mirror")
-
-    def draw_label(self):
-        return "Mirror"
-
-
-class PovrayAmbientNode(Node, ObjectNodeTree):
-    """Ambient"""
-
-    bl_idname = 'PovrayAmbientNode'
-    bl_label = 'Ambient'
-    bl_icon = 'SHADING_SOLID'
-
-    def init(self, context):
-
-        self.inputs.new('PovraySocketColor', "Ambient")
-
-        self.outputs.new('NodeSocketVector', "Ambient")
-
-    def draw_label(self):
-        return "Ambient"
-
-
-class PovrayIridescenceNode(Node, ObjectNodeTree):
-    """Iridescence"""
-
-    bl_idname = 'PovrayIridescenceNode'
-    bl_label = 'Iridescence'
-    bl_icon = 'MESH_UVSPHERE'
-
-    def init(self, context):
-
-        amount = self.inputs.new('NodeSocketFloat', "Amount")
-        amount.default_value = 0.25
-        thickness = self.inputs.new('NodeSocketFloat', "Thickness")
-        thickness.default_value = 1
-        self.inputs.new('NodeSocketFloat', "Turbulence")
-
-        self.outputs.new('NodeSocketVector', "Iridescence")
-
-    def draw_label(self):
-        return "Iridescence"
-
-
-class PovraySubsurfaceNode(Node, ObjectNodeTree):
-    """Subsurface"""
-
-    bl_idname = 'PovraySubsurfaceNode'
-    bl_label = 'Subsurface'
-    bl_icon = 'MESH_UVSPHERE'
-
-    def init(self, context):
-
-        translucency = self.inputs.new('NodeSocketColor', "Translucency")
-        translucency.default_value = (0, 0, 0, 1)
-        energy = self.inputs.new('PovraySocketInt_0_256', "Energy")
-        energy.default_value = 20
-        self.outputs.new('NodeSocketVector', "Translucency")
-
-    def draw_buttons(self, context, layout):
-        scene = context.scene
-        layout.prop(scene.pov, "sslt_enable", text="SSLT")
-
-    def draw_buttons_ext(self, context, layout):
-        scene = context.scene
-        layout.prop(scene.pov, "sslt_enable", text="SSLT")
-
-    def draw_label(self):
-        return "Subsurface"
-
-
-# ---------------------------------------------------------------- #
-
-
-class PovrayMappingNode(Node, ObjectNodeTree):
-    """Mapping"""
-
-    bl_idname = 'PovrayMappingNode'
-    bl_label = 'Mapping'
-    bl_icon = 'NODE_TEXTURE'
-
-    warp_type: EnumProperty(
-        name="Warp Types",
-        description="Select the type of warp",
-        items=(
-            ('cubic', "Cubic", ""),
-            ('cylindrical', "Cylindrical", ""),
-            ('planar', "Planar", ""),
-            ('spherical', "Spherical", ""),
-            ('toroidal', "Toroidal", ""),
-            ('uv_mapping', "UV", ""),
-            ('NONE', "None", "No indentation"),
-        ),
-        default='NONE',
-    )
-
-    warp_orientation: EnumProperty(
-        name="Warp Orientation",
-        description="Select the orientation of warp",
-        items=(('x', "X", ""), ('y', "Y", ""), ('z', "Z", "")),
-        default='y',
-    )
-
-    warp_dist_exp: FloatProperty(
-        name="Distance exponent", description="Distance exponent", min=0.0, max=100.0, default=1.0
-    )
-
-    warp_tor_major_radius: FloatProperty(
-        name="Major radius",
-        description="Torus is distance from major radius",
-        min=0.0,
-        max=5.0,
-        default=1.0,
-    )
-
-    def init(self, context):
-        self.outputs.new('NodeSocketVector', "Mapping")
-
-    def draw_buttons(self, context, layout):
-
-        column = layout.column()
-        column.prop(self, "warp_type", text="Warp type")
-        if self.warp_type in {'toroidal', 'spherical', 'cylindrical', 'planar'}:
-            column.prop(self, "warp_orientation", text="Orientation")
-            column.prop(self, "warp_dist_exp", text="Exponent")
-        if self.warp_type == 'toroidal':
-            column.prop(self, "warp_tor_major_radius", text="Major R")
-
-    def draw_buttons_ext(self, context, layout):
-
-        column = layout.column()
-        column.prop(self, "warp_type", text="Warp type")
-        if self.warp_type in {'toroidal', 'spherical', 'cylindrical', 'planar'}:
-            column.prop(self, "warp_orientation", text="Orientation")
-            column.prop(self, "warp_dist_exp", text="Exponent")
-        if self.warp_type == 'toroidal':
-            column.prop(self, "warp_tor_major_radius", text="Major R")
-
-    def draw_label(self):
-        return "Mapping"
-
-
-class PovrayMultiplyNode(Node, ObjectNodeTree):
-    """Multiply"""
-
-    bl_idname = 'PovrayMultiplyNode'
-    bl_label = 'Multiply'
-    bl_icon = 'SHADING_SOLID'
-
-    amount_x: FloatProperty(
-        name="X", description="Number of repeats", min=1.0, max=10000.0, default=1.0
-    )
-
-    amount_y: FloatProperty(
-        name="Y", description="Number of repeats", min=1.0, max=10000.0, default=1.0
-    )
-
-    amount_z: FloatProperty(
-        name="Z", description="Number of repeats", min=1.0, max=10000.0, default=1.0
-    )
-
-    def init(self, context):
-        self.outputs.new('NodeSocketVector', "Amount")
-
-    def draw_buttons(self, context, layout):
-
-        column = layout.column()
-        column.label(text="Amount")
-        row = column.row(align=True)
-        row.prop(self, "amount_x")
-        row.prop(self, "amount_y")
-        row.prop(self, "amount_z")
-
-    def draw_buttons_ext(self, context, layout):
-
-        column = layout.column()
-        column.label(text="Amount")
-        row = column.row(align=True)
-        row.prop(self, "amount_x")
-        row.prop(self, "amount_y")
-        row.prop(self, "amount_z")
-
-    def draw_label(self):
-        return "Multiply"
-
-
-class PovrayTransformNode(Node, ObjectNodeTree):
-    """Transform"""
-
-    bl_idname = 'PovrayTransformNode'
-    bl_label = 'Transform'
-    bl_icon = 'NODE_TEXTURE'
-
-    def init(self, context):
-
-        self.inputs.new('PovraySocketFloatUnlimited', "Translate x")
-        self.inputs.new('PovraySocketFloatUnlimited', "Translate y")
-        self.inputs.new('PovraySocketFloatUnlimited', "Translate z")
-        self.inputs.new('PovraySocketFloatUnlimited', "Rotate x")
-        self.inputs.new('PovraySocketFloatUnlimited', "Rotate y")
-        self.inputs.new('PovraySocketFloatUnlimited', "Rotate z")
-        sX = self.inputs.new('PovraySocketFloatUnlimited', "Scale x")
-        sX.default_value = 1.0
-        sY = self.inputs.new('PovraySocketFloatUnlimited', "Scale y")
-        sY.default_value = 1.0
-        sZ = self.inputs.new('PovraySocketFloatUnlimited', "Scale z")
-        sZ.default_value = 1.0
-
-        self.outputs.new('NodeSocketVector', "Transform")
-
-    def draw_label(self):
-        return "Transform"
-
-
-class PovrayValueNode(Node, ObjectNodeTree):
-    """Value"""
-
-    bl_idname = 'PovrayValueNode'
-    bl_label = 'Value'
-    bl_icon = 'SHADING_SOLID'
-
-    def init(self, context):
-
-        self.outputs.new('PovraySocketUniversal', "Value")
-
-    def draw_label(self):
-        return "Value"
-
-
-class PovrayModifierNode(Node, ObjectNodeTree):
-    """Modifier"""
-
-    bl_idname = 'PovrayModifierNode'
-    bl_label = 'Modifier'
-    bl_icon = 'NODE_TEXTURE'
-
-    def init(self, context):
-
-        turb_x = self.inputs.new('PovraySocketFloat_0_10', "Turb X")
-        turb_x.default_value = 0.1
-        turb_y = self.inputs.new('PovraySocketFloat_0_10', "Turb Y")
-        turb_y.default_value = 0.1
-        turb_z = self.inputs.new('PovraySocketFloat_0_10', "Turb Z")
-        turb_z.default_value = 0.1
-        octaves = self.inputs.new('PovraySocketInt_1_9', "Octaves")
-        octaves.default_value = 1
-        lambat = self.inputs.new('PovraySocketFloat_0_10', "Lambda")
-        lambat.default_value = 2.0
-        omega = self.inputs.new('PovraySocketFloat_0_10', "Omega")
-        omega.default_value = 0.5
-        freq = self.inputs.new('PovraySocketFloat_0_10', "Frequency")
-        freq.default_value = 2.0
-        self.inputs.new('PovraySocketFloat_0_10', "Phase")
-
-        self.outputs.new('NodeSocketVector', "Modifier")
-
-    def draw_label(self):
-        return "Modifier"
-
-
-class PovrayPigmentNode(Node, ObjectNodeTree):
-    """Pigment"""
-
-    bl_idname = 'PovrayPigmentNode'
-    bl_label = 'Color'
-    bl_icon = 'SHADING_SOLID'
-
-    def init(self, context):
-
-        color = self.inputs.new('PovraySocketColor', "Color")
-        color.default_value = (1, 1, 1)
-        pov_filter = self.inputs.new('PovraySocketFloat_0_1', "Filter")
-        transmit = self.inputs.new('PovraySocketFloat_0_1', "Transmit")
-        self.outputs.new('NodeSocketColor', "Pigment")
-
-    def draw_label(self):
-        return "Color"
-
-
-class PovrayColorImageNode(Node, ObjectNodeTree):
-    """ColorImage"""
-
-    bl_idname = 'PovrayColorImageNode'
-    bl_label = 'Image map'
-
-    map_type: bpy.props.EnumProperty(
-        name="Map type",
-        description="",
-        items=(
-            ('uv_mapping', "UV", ""),
-            ('0', "Planar", "Default planar mapping"),
-            ('1', "Spherical", "Spherical mapping"),
-            ('2', "Cylindrical", "Cylindrical mapping"),
-            ('5', "Torroidal", "Torus or donut shaped mapping"),
-        ),
-        default='0',
-    )
-    image: StringProperty(maxlen=1024)  # , subtype="FILE_PATH"
-    interpolate: EnumProperty(
-        name="Interpolate",
-        description="Adding the interpolate keyword can smooth the jagged look of a bitmap",
-        items=(
-            ('2', "Bilinear", "Gives bilinear interpolation"),
-            ('4', "Normalized", "Gives normalized distance"),
-        ),
-        default='2',
-    )
-    premultiplied: BoolProperty(default=False)
-    once: BoolProperty(description="Not to repeat", default=False)
-
-    def init(self, context):
-
-        gamma = self.inputs.new('PovraySocketFloat_000001_10', "Gamma")
-        gamma.default_value = 2.0
-        transmit = self.inputs.new('PovraySocketFloat_0_1', "Transmit")
-        pov_filter = self.inputs.new('PovraySocketFloat_0_1', "Filter")
-        mapping = self.inputs.new('NodeSocketVector', "Mapping")
-        mapping.hide_value = True
-        transform = self.inputs.new('NodeSocketVector', "Transform")
-        transform.hide_value = True
-        modifier = self.inputs.new('NodeSocketVector', "Modifier")
-        modifier.hide_value = True
-
-        self.outputs.new('NodeSocketColor', "Pigment")
-
-    def draw_buttons(self, context, layout):
-
-        column = layout.column()
-        im = None
-        for image in bpy.data.images:
-            if image.name == self.image:
-                im = image
-        split = column.split(factor=0.8, align=True)
-        split.prop_search(self, "image", context.blend_data, "images", text="")
-        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
-        if im is not None:
-            column.prop(im, "source", text="")
-        column.prop(self, "map_type", text="")
-        column.prop(self, "interpolate", text="")
-        row = column.row()
-        row.prop(self, "premultiplied", text="Premul")
-        row.prop(self, "once", text="Once")
-
-    def draw_buttons_ext(self, context, layout):
-
-        column = layout.column()
-        im = None
-        for image in bpy.data.images:
-            if image.name == self.image:
-                im = image
-        split = column.split(factor=0.8, align=True)
-        split.prop_search(self, "image", context.blend_data, "images", text="")
-        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
-        if im is not None:
-            column.prop(im, "source", text="")
-        column.prop(self, "map_type", text="")
-        column.prop(self, "interpolate", text="")
-        row = column.row()
-        row.prop(self, "premultiplied", text="Premul")
-        row.prop(self, "once", text="Once")
-
-    def draw_label(self):
-        return "Image map"
-
-
-class PovrayBumpMapNode(Node, ObjectNodeTree):
-    """BumpMap"""
-
-    bl_idname = 'PovrayBumpMapNode'
-    bl_label = 'Bump map'
-    bl_icon = 'TEXTURE'
-
-    map_type: bpy.props.EnumProperty(
-        name="Map type",
-        description="",
-        items=(
-            ('uv_mapping', "UV", ""),
-            ('0', "Planar", "Default planar mapping"),
-            ('1', "Spherical", "Spherical mapping"),
-            ('2', "Cylindrical", "Cylindrical mapping"),
-            ('5', "Torroidal", "Torus or donut shaped mapping"),
-        ),
-        default='0',
-    )
-    image: StringProperty(maxlen=1024)  # , subtype="FILE_PATH"
-    interpolate: EnumProperty(
-        name="Interpolate",
-        description="Adding the interpolate keyword can smooth the jagged look of a bitmap",
-        items=(
-            ('2', "Bilinear", "Gives bilinear interpolation"),
-            ('4', "Normalized", "Gives normalized distance"),
-        ),
-        default='2',
-    )
-    once: BoolProperty(description="Not to repeat", default=False)
-
-    def init(self, context):
-
-        self.inputs.new('PovraySocketFloat_0_10', "Normal")
-        mapping = self.inputs.new('NodeSocketVector', "Mapping")
-        mapping.hide_value = True
-        transform = self.inputs.new('NodeSocketVector', "Transform")
-        transform.hide_value = True
-        modifier = self.inputs.new('NodeSocketVector', "Modifier")
-        modifier.hide_value = True
-
-        normal = self.outputs.new('NodeSocketFloat', "Normal")
-        normal.hide_value = True
-
-    def draw_buttons(self, context, layout):
-
-        column = layout.column()
-        im = None
-        for image in bpy.data.images:
-            if image.name == self.image:
-                im = image
-        split = column.split(factor=0.8, align=True)
-        split.prop_search(self, "image", context.blend_data, "images", text="")
-        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
-        if im is not None:
-            column.prop(im, "source", text="")
-        column.prop(self, "map_type", text="")
-        column.prop(self, "interpolate", text="")
-        column.prop(self, "once", text="Once")
-
-    def draw_buttons_ext(self, context, layout):
-
-        column = layout.column()
-        im = None
-        for image in bpy.data.images:
-            if image.name == self.image:
-                im = image
-        split = column.split(factor=0.8, align=True)
-        split.prop_search(self, "image", context.blend_data, "images", text="")
-        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
-        if im is not None:
-            column.prop(im, "source", text="")
-        column.prop(self, "map_type", text="")
-        column.prop(self, "interpolate", text="")
-        column.prop(self, "once", text="Once")
-
-    def draw_label(self):
-        return "Bump Map"
-
-
-class PovrayImagePatternNode(Node, ObjectNodeTree):
-    """ImagePattern"""
-
-    bl_idname = 'PovrayImagePatternNode'
-    bl_label = 'Image pattern'
-    bl_icon = 'NODE_TEXTURE'
-
-    map_type: bpy.props.EnumProperty(
-        name="Map type",
-        description="",
-        items=(
-            ('uv_mapping', "UV", ""),
-            ('0', "Planar", "Default planar mapping"),
-            ('1', "Spherical", "Spherical mapping"),
-            ('2', "Cylindrical", "Cylindrical mapping"),
-            ('5', "Torroidal", "Torus or donut shaped mapping"),
-        ),
-        default='0',
-    )
-    image: StringProperty(maxlen=1024)  # , subtype="FILE_PATH"
-    interpolate: EnumProperty(
-        name="Interpolate",
-        description="Adding the interpolate keyword can smooth the jagged look of a bitmap",
-        items=(
-            ('2', "Bilinear", "Gives bilinear interpolation"),
-            ('4', "Normalized", "Gives normalized distance"),
-        ),
-        default='2',
-    )
-    premultiplied: BoolProperty(default=False)
-    once: BoolProperty(description="Not to repeat", default=False)
-    use_alpha: BoolProperty(default=True)
-
-    def init(self, context):
-
-        gamma = self.inputs.new('PovraySocketFloat_000001_10', "Gamma")
-        gamma.default_value = 2.0
-
-        self.outputs.new('PovraySocketPattern', "Pattern")
-
-    def draw_buttons(self, context, layout):
-
-        column = layout.column()
-        im = None
-        for image in bpy.data.images:
-            if image.name == self.image:
-                im = image
-        split = column.split(factor=0.8, align=True)
-        split.prop_search(self, "image", context.blend_data, "images", text="")
-        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
-        if im is not None:
-            column.prop(im, "source", text="")
-        column.prop(self, "map_type", text="")
-        column.prop(self, "interpolate", text="")
-        row = column.row()
-        row.prop(self, "premultiplied", text="Premul")
-        row.prop(self, "once", text="Once")
-        column.prop(self, "use_alpha", text="Use alpha")
-
-    def draw_buttons_ext(self, context, layout):
-
-        column = layout.column()
-        im = None
-        for image in bpy.data.images:
-            if image.name == self.image:
-                im = image
-        split = column.split(factor=0.8, align=True)
-        split.prop_search(self, "image", context.blend_data, "images", text="")
-        split.operator("pov.imageopen", text="", icon="FILEBROWSER")
-        if im is not None:
-            column.prop(im, "source", text="")
-        column.prop(self, "map_type", text="")
-        column.prop(self, "interpolate", text="")
-        row = column.row()
-        row.prop(self, "premultiplied", text="Premul")
-        row.prop(self, "once", text="Once")
-
-    def draw_label(self):
-        return "Image pattern"
-
-
-class ShaderPatternNode(Node, ObjectNodeTree):
-    """Pattern"""
-
-    bl_idname = 'ShaderPatternNode'
-    bl_label = 'Other patterns'
-
-    pattern: EnumProperty(
-        name="Pattern",
-        description="Agate, Crackle, Gradient, Pavement, Spiral, Tiling",
-        items=(
-            ('agate', "Agate", ""),
-            ('crackle', "Crackle", ""),
-            ('gradient', "Gradient", ""),
-            ('pavement', "Pavement", ""),
-            ('spiral1', "Spiral 1", ""),
-            ('spiral2', "Spiral 2", ""),
-            ('tiling', "Tiling", ""),
-        ),
-        default='agate',
-    )
-
-    agate_turb: FloatProperty(
-        name="Agate turb", description="Agate turbulence", min=0.0, max=100.0, default=0.5
-    )
-
-    crackle_form_x: FloatProperty(
-        name="X", description="Form vector X", min=-150.0, max=150.0, default=-1
-    )
-
-    crackle_form_y: FloatProperty(
-        name="Y", description="Form vector Y", min=-150.0, max=150.0, default=1
-    )
-
-    crackle_form_z: FloatProperty(
-        name="Z", description="Form vector Z", min=-150.0, max=150.0, default=0
-    )
-
-    crackle_metric: FloatProperty(
-        name="Metric", description="Crackle metric", min=0.0, max=150.0, default=1
-    )
-
-    crackle_solid: BoolProperty(name="Solid", description="Crackle solid", default=False)
-
-    spiral_arms: FloatProperty(name="Number", description="", min=0.0, max=256.0, default=2.0)
-
-    tiling_number: IntProperty(name="Number", description="", min=1, max=27, default=1)
-
-    gradient_orient: EnumProperty(
-        name="Orient",
-        description="",
-        items=(('x', "X", ""), ('y', "Y", ""), ('z', "Z", "")),
-        default='x',
-    )
-
-    def init(self, context):
-
-        pat = self.outputs.new('PovraySocketPattern', "Pattern")
-
-    def draw_buttons(self, context, layout):
-
-        layout.prop(self, "pattern", text="")
-        if self.pattern == 'agate':
-            layout.prop(self, "agate_turb")
-        if self.pattern == 'crackle':
-            layout.prop(self, "crackle_metric")
-            layout.prop(self, "crackle_solid")
-            layout.label(text="Form:")
-            layout.prop(self, "crackle_form_x")
-            layout.prop(self, "crackle_form_y")
-            layout.prop(self, "crackle_form_z")
-        if self.pattern in {"spiral1", "spiral2"}:
-            layout.prop(self, "spiral_arms")
-        if self.pattern in {'tiling'}:
-            layout.prop(self, "tiling_number")
-        if self.pattern in {'gradient'}:
-            layout.prop(self, "gradient_orient")
-
-    def draw_buttons_ext(self, context, layout):
-        pass
-
-    def draw_label(self):
-        return "Other patterns"
-
-
-class ShaderTextureMapNode(Node, ObjectNodeTree):
-    """Texture Map"""
-
-    bl_idname = 'ShaderTextureMapNode'
-    bl_label = 'Texture map'
-
-    brick_size_x: FloatProperty(name="X", description="", min=0.0000, max=1.0000, default=0.2500)
-
-    brick_size_y: FloatProperty(name="Y", description="", min=0.0000, max=1.0000, default=0.0525)
-
-    brick_size_z: FloatProperty(name="Z", description="", min=0.0000, max=1.0000, default=0.1250)
-
-    brick_mortar: FloatProperty(
-        name="Mortar", description="Mortar", min=0.000, max=1.500, default=0.01
-    )
-
-    def init(self, context):
-        mat = bpy.context.object.active_material
-        self.inputs.new('PovraySocketPattern', "")
-        color = self.inputs.new('NodeSocketColor', "Color ramp")
-        color.hide_value = True
-        for i in range(0, 4):
-            transform = self.inputs.new('PovraySocketTransform', "Transform")
-            transform.hide_value = True
-        number = mat.pov.inputs_number
-        for i in range(number):
-            self.inputs.new('PovraySocketTexture', "%s" % i)
-
-        self.outputs.new('PovraySocketTexture', "Texture")
-
-    def draw_buttons(self, context, layout):
-
-        if self.inputs[0].default_value == 'brick':
-            layout.prop(self, "brick_mortar")
-            layout.label(text="Brick size:")
-            layout.prop(self, "brick_size_x")
-            layout.prop(self, "brick_size_y")
-            layout.prop(self, "brick_size_z")
-
-    def draw_buttons_ext(self, context, layout):
-
-        if self.inputs[0].default_value == 'brick':
-            layout.prop(self, "brick_mortar")
-            layout.label(text="Brick size:")
-            layout.prop(self, "brick_size_x")
-            layout.prop(self, "brick_size_y")
-            layout.prop(self, "brick_size_z")
-
-    def draw_label(self):
-        return "Texture map"
-
-
-class ShaderNormalMapNode(Node, ObjectNodeTree):
-    """Normal Map"""
-
-    bl_idname = 'ShaderNormalMapNode'
-    bl_label = 'Normal map'
-
-    brick_size_x: FloatProperty(name="X", description="", min=0.0000, max=1.0000, default=0.2500)
-
-    brick_size_y: FloatProperty(name="Y", description="", min=0.0000, max=1.0000, default=0.0525)
-
-    brick_size_z: FloatProperty(name="Z", description="", min=0.0000, max=1.0000, default=0.1250)
-
-    brick_mortar: FloatProperty(
-        name="Mortar", description="Mortar", min=0.000, max=1.500, default=0.01
-    )
-
-    def init(self, context):
-        self.inputs.new('PovraySocketPattern', "")
-        normal = self.inputs.new('PovraySocketFloat_10', "Normal")
-        slope = self.inputs.new('PovraySocketMap', "Slope map")
-        for i in range(0, 4):
-            transform = self.inputs.new('PovraySocketTransform', "Transform")
-            transform.hide_value = True
-        self.outputs.new('PovraySocketNormal', "Normal")
-
-    def draw_buttons(self, context, layout):
-        # for i, inp in enumerate(self.inputs):
-
-        if self.inputs[0].default_value == 'brick':
-            layout.prop(self, "brick_mortar")
-            layout.label(text="Brick size:")
-            layout.prop(self, "brick_size_x")
-            layout.prop(self, "brick_size_y")
-            layout.prop(self, "brick_size_z")
-
-    def draw_buttons_ext(self, context, layout):
-
-        if self.inputs[0].default_value == 'brick':
-            layout.prop(self, "brick_mortar")
-            layout.label(text="Brick size:")
-            layout.prop(self, "brick_size_x")
-            layout.prop(self, "brick_size_y")
-            layout.prop(self, "brick_size_z")
-
-    def draw_label(self):
-        return "Normal map"
-
-
-class ShaderNormalMapEntryNode(Node, ObjectNodeTree):
-    """Normal Map Entry"""
-
-    bl_idname = 'ShaderNormalMapEntryNode'
-    bl_label = 'Normal map entry'
-
-    def init(self, context):
-        self.inputs.new('PovraySocketFloat_0_1', "Stop")
-        self.inputs.new('PovraySocketFloat_0_1', "Gray")
-
-    def draw_label(self):
-        return "Normal map entry"
-
-
-class IsoPropsNode(Node, CompositorNodeTree):
-    """ISO Props"""
-
-    bl_idname = 'IsoPropsNode'
-    bl_label = 'Iso'
-    node_label: StringProperty(maxlen=1024)
-
-    def init(self, context):
-        ob = bpy.context.object
-        self.node_label = ob.name
-        text_name = ob.pov.function_text
-        if text_name:
-            text = bpy.data.texts[text_name]
-            for line in text.lines:
-                split = line.body.split()
-                if split[0] == "#declare":
-                    socket = self.inputs.new('NodeSocketFloat', "%s" % split[1])
-                    value = split[3].split(";")
-                    value = value[0]
-                    socket.default_value = float(value)
-
-    def draw_label(self):
-        return self.node_label
-
-
-class PovrayFogNode(Node, CompositorNodeTree):
-    """Fog settings"""
-
-    bl_idname = 'PovrayFogNode'
-    bl_label = 'Fog'
-
-    def init(self, context):
-        color = self.inputs.new('NodeSocketColor', "Color")
-        color.default_value = (0.7, 0.7, 0.7, 0.25)
-        self.inputs.new('PovraySocketFloat_0_1', "Filter")
-        distance = self.inputs.new('NodeSocketInt', "Distance")
-        distance.default_value = 150
-        self.inputs.new('NodeSocketBool', "Ground")
-        fog_offset = self.inputs.new('NodeSocketFloat', "Offset")
-        fog_alt = self.inputs.new('NodeSocketFloat', "Altitude")
-        turb = self.inputs.new('NodeSocketVector', "Turbulence")
-        turb_depth = self.inputs.new('PovraySocketFloat_0_10', "Depth")
-        turb_depth.default_value = 0.5
-        octaves = self.inputs.new('PovraySocketInt_1_9', "Octaves")
-        octaves.default_value = 5
-        lambdat = self.inputs.new('PovraySocketFloat_0_10', "Lambda")
-        lambdat.default_value = 1.25
-        omega = self.inputs.new('PovraySocketFloat_0_10', "Omega")
-        omega.default_value = 0.35
-        translate = self.inputs.new('NodeSocketVector', "Translate")
-        rotate = self.inputs.new('NodeSocketVector', "Rotate")
-        scale = self.inputs.new('NodeSocketVector', "Scale")
-        scale.default_value = (1, 1, 1)
-
-    def draw_label(self):
-        return "Fog"
-
-
-class PovraySlopeNode(Node, TextureNodeTree):
-    """Output"""
-
-    bl_idname = 'PovraySlopeNode'
-    bl_label = 'Slope Map'
-
-    def init(self, context):
-        self.use_custom_color = True
-        self.color = (0, 0.2, 0)
-        slope = self.inputs.new('PovraySocketSlope', "0")
-        slope = self.inputs.new('PovraySocketSlope', "1")
-        slopemap = self.outputs.new('PovraySocketMap', "Slope map")
-        output.hide_value = True
-
-    def draw_buttons(self, context, layout):
-
-        layout.operator("pov.nodeinputadd")
-        row = layout.row()
-        row.label(text='Value')
-        row.label(text='Height')
-        row.label(text='Slope')
-
-    def draw_buttons_ext(self, context, layout):
-
-        layout.operator("pov.nodeinputadd")
-        row = layout.row()
-        row.label(text='Value')
-        row.label(text='Height')
-        row.label(text='Slope')
-
-    def draw_label(self):
-        return "Slope Map"
-
-
-# -------- Texture nodes # ---------------------------------------------------------------- #
-class TextureOutputNode(Node, TextureNodeTree):
-    """Output"""
-
-    bl_idname = 'TextureOutputNode'
-    bl_label = 'Color Map'
-
-    def init(self, context):
-        tex = bpy.context.object.active_material.active_texture
-        num_sockets = int(tex.pov.density_lines / 32)
-        for i in range(num_sockets):
-            color = self.inputs.new('NodeSocketColor', "%s" % i)
-            color.hide_value = True
-
-    def draw_buttons(self, context, layout):
-
-        layout.label(text="Color Ramps:")
-
-    def draw_label(self):
-        return "Color Map"
-
-
-# ------------------------------------------------------------------------------ #
-# --------------------------------- Operators ---------------------------------- #
-# ------------------------------------------------------------------------------ #
-
-
-class NODE_OT_iso_add(Operator):
-    bl_idname = "pov.nodeisoadd"
-    bl_label = "Create iso props"
-
-    def execute(self, context):
-        ob = bpy.context.object
-        if not bpy.context.scene.use_nodes:
-            bpy.context.scene.use_nodes = True
-        tree = bpy.context.scene.node_tree
-        for node in tree.nodes:
-            if node.bl_idname == "IsoPropsNode" and node.label == ob.name:
-                tree.nodes.remove(node)
-        isonode = tree.nodes.new('IsoPropsNode')
-        isonode.location = (0, 0)
-        isonode.label = ob.name
-        return {'FINISHED'}
-
-
-class NODE_OT_map_create(Operator):
-    bl_idname = "node.map_create"
-    bl_label = "Create map"
-
-    def execute(self, context):
-        x = y = 0
-        space = context.space_data
-        tree = space.edit_tree
-        for node in tree.nodes:
-            if node.select:
-                x, y = node.location
-            node.select = False
-        tmap = tree.nodes.new('ShaderTextureMapNode')
-        tmap.location = (x - 200, y)
-        return {'FINISHED'}
-
-    def invoke(self, context, event):
-        wm = context.window_manager
-        return wm.invoke_props_dialog(self)
-
-    def draw(self, context):
-        layout = self.layout
-        mat = context.object.active_material
-        layout.prop(mat.pov, "inputs_number")
-
-
-class NODE_OT_povray_node_texture_map_add(Operator):
-    bl_idname = "pov.nodetexmapadd"
-    bl_label = "Texture map"
-
-    def execute(self, context):
-        tree = bpy.context.object.active_material.node_tree
-        tmap = tree.nodes.active
-        bpy.context.object.active_material.node_tree.nodes.active = tmap
-        el = tmap.color_ramp.elements.new(0.5)
-        for el in tmap.color_ramp.elements:
-            el.color = (0, 0, 0, 1)
-        for inp in tmap.inputs:
-            tmap.inputs.remove(inp)
-        for outp in tmap.outputs:
-            tmap.outputs.remove(outp)
-        pattern = tmap.inputs.new('NodeSocketVector', "Pattern")
-        pattern.hide_value = True
-        for i in range(0, 3):
-            tmap.inputs.new('NodeSocketColor', "Shader")
-        tmap.outputs.new('NodeSocketShader', "BSDF")
-        tmap.label = "Texture Map"
-        return {'FINISHED'}
-
-
-class NODE_OT_povray_node_output_add(Operator):
-    bl_idname = "pov.nodeoutputadd"
-    bl_label = "Output"
-
-    def execute(self, context):
-        tree = bpy.context.object.active_material.node_tree
-        tmap = tree.nodes.new('ShaderNodeOutputMaterial')
-        bpy.context.object.active_material.node_tree.nodes.active = tmap
-        for inp in tmap.inputs:
-            tmap.inputs.remove(inp)
-        tmap.inputs.new('NodeSocketShader', "Surface")
-        tmap.label = "Output"
-        return {'FINISHED'}
-
-
-class NODE_OT_povray_node_layered_add(Operator):
-    bl_idname = "pov.nodelayeredadd"
-    bl_label = "Layered material"
-
-    def execute(self, context):
-        tree = bpy.context.object.active_material.node_tree
-        tmap = tree.nodes.new('ShaderNodeAddShader')
-        bpy.context.object.active_material.node_tree.nodes.active = tmap
-        tmap.label = "Layered material"
-        return {'FINISHED'}
-
-
-class NODE_OT_povray_input_add(Operator):
-    bl_idname = "pov.nodeinputadd"
-    bl_label = "Add entry"
-
-    def execute(self, context):
-        node = bpy.context.object.active_material.node_tree.nodes.active
-        if node.type in {'VALTORGB'}:
-            number = 1
-            for inp in node.inputs:
-                if inp.type == 'SHADER':
-                    number += 1
-            node.inputs.new('NodeSocketShader', "%s" % number)
-            els = node.color_ramp.elements
-            pos1 = els[len(els) - 1].position
-            pos2 = els[len(els) - 2].position
-            pos = (pos1 - pos2) / 2 + pos2
-            el = els.new(pos)
-
-        if node.bl_idname == 'PovraySlopeNode':
-            number = len(node.inputs)
-            node.inputs.new('PovraySocketSlope', "%s" % number)
-
-        return {'FINISHED'}
-
-
-class NODE_OT_povray_input_remove(Operator):
-    bl_idname = "pov.nodeinputremove"
-    bl_label = "Remove input"
-
-    def execute(self, context):
-        node = bpy.context.object.active_material.node_tree.nodes.active
-        if node.type in {'VALTORGB', 'ADD_SHADER'}:
-            number = len(node.inputs) - 1
-            if number > 5:
-                inp = node.inputs[number]
-                node.inputs.remove(inp)
-                if node.type in {'VALTORGB'}:
-                    els = node.color_ramp.elements
-                    number = len(els) - 2
-                    el = els[number]
-                    els.remove(el)
-        return {'FINISHED'}
-
-
-class NODE_OT_povray_image_open(Operator):
-    bl_idname = "pov.imageopen"
-    bl_label = "Open"
-
-    filepath: StringProperty(
-        name="File Path", description="Open image", maxlen=1024, subtype='FILE_PATH'
-    )
-
-    def invoke(self, context, event):
-        context.window_manager.fileselect_add(self)
-        return {'RUNNING_MODAL'}
-
-    def execute(self, context):
-        im = bpy.data.images.load(self.filepath)
-        node = context.object.active_material.node_tree.nodes.active
-        node.image = im.name
-        return {'FINISHED'}
-
-
-# class TEXTURE_OT_povray_open_image(Operator):
-# bl_idname = "pov.openimage"
-# bl_label = "Open Image"
-
-# filepath = StringProperty(
-# name="File Path",
-# description="Open image",
-# maxlen=1024,
-# subtype='FILE_PATH',
-# )
-
-# def invoke(self, context, event):
-# context.window_manager.fileselect_add(self)
-# return {'RUNNING_MODAL'}
-
-# def execute(self, context):
-# im=bpy.data.images.load(self.filepath)
-# tex = context.texture
-# tex.pov.image = im.name
-# view_layer = context.view_layer
-# view_layer.update()
-# return {'FINISHED'}
-
-
-class PovrayPatternNode(Operator):
-    bl_idname = "pov.patternnode"
-    bl_label = "Pattern"
-
-    add = True
-
-    def execute(self, context):
-        space = context.space_data
-        tree = space.edit_tree
-        for node in tree.nodes:
-            node.select = False
-        if self.add:
-            tmap = tree.nodes.new('ShaderNodeValToRGB')
-            tmap.label = "Pattern"
-            for inp in tmap.inputs:
-                tmap.inputs.remove(inp)
-            for outp in tmap.outputs:
-                tmap.outputs.remove(outp)
-            pattern = tmap.inputs.new('PovraySocketPattern', "Pattern")
-            pattern.hide_value = True
-            mapping = tmap.inputs.new('NodeSocketVector', "Mapping")
-            mapping.hide_value = True
-            transform = tmap.inputs.new('NodeSocketVector', "Transform")
-            transform.hide_value = True
-            modifier = tmap.inputs.new('NodeSocketVector', "Modifier")
-            modifier.hide_value = True
-            for i in range(0, 2):
-                tmap.inputs.new('NodeSocketShader', "%s" % (i + 1))
-            tmap.outputs.new('NodeSocketShader', "Material")
-            tmap.outputs.new('NodeSocketColor', "Color")
-            tree.nodes.active = tmap
-            self.add = False
-        aNode = tree.nodes.active
-        aNode.select = True
-        v2d = context.region.view2d
-        x, y = v2d.region_to_view(self.x, self.y)
-        aNode.location = (x, y)
-
-    def modal(self, context, event):
-        if event.type == 'MOUSEMOVE':
-            self.x = event.mouse_region_x
-            self.y = event.mouse_region_y
-            self.execute(context)
-            return {'RUNNING_MODAL'}
-        elif event.type == 'LEFTMOUSE':
-            return {'FINISHED'}
-        elif event.type in ('RIGHTMOUSE', 'ESC'):
-            return {'CANCELLED'}
-
-        return {'RUNNING_MODAL'}
-
-    def invoke(self, context, event):
-        context.window_manager.modal_handler_add(self)
-        return {'RUNNING_MODAL'}
-
-
-class UpdatePreviewMaterial(Operator):
-    """Operator update preview material"""
-
-    bl_idname = "node.updatepreview"
-    bl_label = "Update preview"
-
-    def execute(self, context):
-        scene = context.view_layer
-        ob = context.object
-        for obj in scene.objects:
-            if obj != ob:
-                scene.objects.active = ob
-                break
-        scene.objects.active = ob
-
-    def modal(self, context, event):
-        if event.type == 'RIGHTMOUSE':
-            self.execute(context)
-            return {'FINISHED'}
-        return {'PASS_THROUGH'}
-
-    def invoke(self, context, event):
-        context.window_manager.modal_handler_add(self)
-        return {'RUNNING_MODAL'}
-
-
-class UpdatePreviewKey(Operator):
-    """Operator update preview keymap"""
-
-    bl_idname = "wm.updatepreviewkey"
-    bl_label = "Activate RMB"
-
-    @classmethod
-    def poll(cls, context):
-        conf = context.window_manager.keyconfigs.active
-        mapstr = "Node Editor"
-        map = conf.keymaps[mapstr]
-        try:
-            map.keymap_items["node.updatepreview"]
-            return False
-        except BaseException as e:
-            print(e.__doc__)
-            print('An exception occurred: {}'.format(e))
-            return True
-
-    def execute(self, context):
-        conf = context.window_manager.keyconfigs.active
-        mapstr = "Node Editor"
-        map = conf.keymaps[mapstr]
-        map.keymap_items.new("node.updatepreview", type='RIGHTMOUSE', value="PRESS")
-        return {'FINISHED'}
-
-
-classes = (
-    PovraySocketUniversal,
-    PovraySocketFloat_0_1,
-    PovraySocketFloat_0_10,
-    PovraySocketFloat_10,
-    PovraySocketFloatPositive,
-    PovraySocketFloat_000001_10,
-    PovraySocketFloatUnlimited,
-    PovraySocketInt_1_9,
-    PovraySocketInt_0_256,
-    PovraySocketPattern,
-    PovraySocketColor,
-    PovraySocketColorRGBFT,
-    PovraySocketTexture,
-    PovraySocketTransform,
-    PovraySocketNormal,
-    PovraySocketSlope,
-    PovraySocketMap,
-    # PovrayShaderNodeCategory, # XXX SOMETHING BROKEN from 2.8 ?
-    # PovrayTextureNodeCategory, # XXX SOMETHING BROKEN from 2.8 ?
-    # PovraySceneNodeCategory, # XXX SOMETHING BROKEN from 2.8 ?
-    NODE_MT_POV_map_create,
-    ObjectNodeTree,
-    PovrayOutputNode,
-    PovrayTextureNode,
-    PovrayFinishNode,
-    PovrayDiffuseNode,
-    PovrayPhongNode,
-    PovraySpecularNode,
-    PovrayMirrorNode,
-    PovrayAmbientNode,
-    PovrayIridescenceNode,
-    PovraySubsurfaceNode,
-    PovrayMappingNode,
-    PovrayMultiplyNode,
-    PovrayTransformNode,
-    PovrayValueNode,
-    PovrayModifierNode,
-    PovrayPigmentNode,
-    PovrayColorImageNode,
-    PovrayBumpMapNode,
-    PovrayImagePatternNode,
-    ShaderPatternNode,
-    ShaderTextureMapNode,
-    ShaderNormalMapNode,
-    ShaderNormalMapEntryNode,
-    IsoPropsNode,
-    PovrayFogNode,
-    PovraySlopeNode,
-    TextureOutputNode,
-    NODE_OT_iso_add,
-    NODE_OT_map_create,
-    NODE_OT_povray_node_texture_map_add,
-    NODE_OT_povray_node_output_add,
-    NODE_OT_povray_node_layered_add,
-    NODE_OT_povray_input_add,
-    NODE_OT_povray_input_remove,
-    NODE_OT_povray_image_open,
-    PovrayPatternNode,
-    UpdatePreviewMaterial,
-    UpdatePreviewKey,
-)
-
-
-def register():
-    bpy.types.NODE_HT_header.append(menu_func_nodes)
-    nodeitems_utils.register_node_categories("POVRAYNODES", node_categories)
-    for cls in classes:
-        register_class(cls)
-
-
-def unregister():
-    for cls in reversed(classes):
-        unregister_class(cls)
-    nodeitems_utils.unregister_node_categories("POVRAYNODES")
-    bpy.types.NODE_HT_header.remove(menu_func_nodes)
diff --git a/render_povray/shading_properties.py b/render_povray/shading_properties.py
index bcc8944d7..a1c358dee 100755
--- a/render_povray/shading_properties.py
+++ b/render_povray/shading_properties.py
@@ -19,10 +19,7 @@ from bpy.props import (
 def check_material(mat):
     """Check that material node tree is not empty if use node button is on"""
     if mat is not None:
-        if mat.use_nodes:
-            # No active node material
-            return not mat.node_tree
-        return True
+        return not mat.node_tree if mat.use_nodes else True
     return False
 
 
@@ -30,28 +27,27 @@ def pov_context_tex_datablock(context):
     """Texture context type recreated as deprecated in blender 2.8"""
 
     idblock = context.brush
-    if idblock and context.scene.texture_context == 'OTHER':
+    if idblock and context.scene.texture_context == "OTHER":
         return idblock
 
     # idblock = bpy.context.active_object.active_material
     idblock = context.view_layer.objects.active.active_material
-    if idblock and context.scene.texture_context == 'MATERIAL':
+    if idblock and context.scene.texture_context == "MATERIAL":
         return idblock
 
     idblock = context.scene.world
-    if idblock and context.scene.texture_context == 'WORLD':
+    if idblock and context.scene.texture_context == "WORLD":
         return idblock
 
     idblock = context.light
-    if idblock and context.scene.texture_context == 'LIGHT':
+    if idblock and context.scene.texture_context == "LIGHT":
         return idblock
 
-    if context.particle_system and context.scene.texture_context == 'PARTICLES':
+    if context.particle_system and context.scene.texture_context == "PARTICLES":
         idblock = context.particle_system.settings
 
-
     idblock = context.line_style
-    if idblock and context.scene.texture_context == 'LINESTYLE':
+    if idblock and context.scene.texture_context == "LINESTYLE":
         return idblock
 
     return idblock
@@ -88,7 +84,7 @@ def active_texture_name_from_search(self, context):
         # bpy.context.tool_settings.image_paint.brush.mask_texture = tex
     except BaseException as e:
         print(e.__doc__)
-        print('An exception occurred: {}'.format(e))
+        print("An exception occurred: {}".format(e))
 
 
 def brush_texture_update(self, context):
@@ -99,10 +95,9 @@ def brush_texture_update(self, context):
         # mat = context.view_layer.objects.active.active_material
         idblock = pov_context_tex_datablock(context)
         slot = idblock.pov_texture_slots[idblock.pov.active_texture_index]
-        tex = slot.texture
-
-        if tex:
+        if tex := slot.texture:
             # Switch paint brush to active texture so slot and settings remain contextual
+            # bpy.ops.pov.textureslotupdate()
             bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[tex]
             bpy.context.tool_settings.image_paint.brush.mask_texture = bpy.data.textures[tex]
 
@@ -200,7 +195,7 @@ class RenderPovSettingsMaterial(PropertyGroup):
         default=1.0,
         precision=3,
     )
-
+    # TODO: replace by newer agnostic Material.diffuse_color and remove from pov panel
     diffuse_color: FloatVectorProperty(
         name="Diffuse color",
         description="Diffuse color of the material",
@@ -414,7 +409,6 @@ class RenderPovSettingsMaterial(PropertyGroup):
         options={"ANIMATABLE"},
         subtype="COLOR",
     )
-
     specular_hardness: IntProperty(
         name="Hardness",
         description="How hard (sharp) the specular reflection is",
@@ -422,7 +416,7 @@ class RenderPovSettingsMaterial(PropertyGroup):
         max=511,
         default=30,
     )
-
+    # TODO: replace by newer agnostic Material.specular_intensity and remove from pov panel
     specular_intensity: FloatProperty(
         name="Intensity",
         description="How intense (bright) the specular reflection is",
@@ -642,6 +636,7 @@ class RenderPovSettingsMaterial(PropertyGroup):
         default=False,
     )
 
+    # TODO create interface:
     use_vertex_color_paint: BoolProperty(
         name="Vertex Color Paint",
         description="Replace object base color with vertex "
@@ -713,12 +708,6 @@ class RenderPovSettingsMaterial(PropertyGroup):
         default=False,
     )
 
-    mirror_metallic: BoolProperty(
-        name="Metallic Reflection",
-        description="mirror reflections get colored as diffuse (for metallic materials)",
-        default=False,
-    )
-
     conserve_energy: BoolProperty(
         name="Conserve Energy",
         description="Light transmitted is more correctly reduced by mirror reflections, "
@@ -780,7 +769,9 @@ class RenderPovSettingsMaterial(PropertyGroup):
     )
 
     fake_caustics: BoolProperty(
-        name="Fake Caustics", description="use only (Fast) fake refractive caustics", default=True
+        name="Fake Caustics",
+        description="use only (Fast) fake refractive caustics based on transparent shadows",
+        default=True
     )
 
     fake_caustics_power: FloatProperty(
@@ -872,7 +863,7 @@ class RenderPovSettingsMaterial(PropertyGroup):
                 for node in tree.nodes:
                     if node.bl_idname == "PovrayOutputNode":
                         o += 1
-                    if node.bl_idname == "PovrayTextureNode":
+                    elif node.bl_idname == "PovrayTextureNode":
                         m += 1
                 if o == 1 and m == 1:
                     default = False
@@ -926,15 +917,16 @@ class RenderPovSettingsMaterial(PropertyGroup):
 
     def pigment_normal_callback(self, context):
         render = context.scene.pov.render  # XXX comment out > remove?
-        items = [("pigment", "Pigment", ""), ("normal", "Normal", "")]
-        # XXX Find any other such traces of hgpovray experiment > remove or deploy ?
-        if render == "hgpovray":
-            items = [
+        return (
+            [("pigment", "Pigment", ""), ("normal", "Normal", "")]
+            # XXX Find any other such traces of hgpovray experiment > remove or deploy ?
+            if render != "hgpovray"
+            else [
                 ("pigment", "Pigment", ""),
                 ("normal", "Normal", ""),
                 ("modulation", "Modulation", ""),
             ]
-        return items
+        )
 
     def glow_callback(self, context):
         scene = context.scene
@@ -966,1305 +958,23 @@ class RenderPovSettingsMaterial(PropertyGroup):
     object_preview_bgcontrast: FloatProperty(name="Contrast", min=0.0, max=1.0, default=0.5)
 
 
-class MaterialRaytraceTransparency(PropertyGroup):
-    """Declare transparency panel properties controllable in UI and translated to POV."""
-
-    depth: IntProperty(
-        name="Depth",
-        description="Maximum allowed number of light inter-refractions",
-        min=0,
-        max=32767,
-        default=2,
-    )
-
-    depth_max: FloatProperty(
-        name="Depth",
-        description="Maximum depth for light to travel through the "
-        "transparent material before becoming fully filtered (0.0 is disabled)",
-        min=0,
-        max=100,
-        default=0.0,
-    )
-
-    falloff: FloatProperty(
-        name="Falloff",
-        description="Falloff power for transmissivity filter effect (1.0 is linear)",
-        min=0.1,
-        max=10.0,
-        default=1.0,
-        precision=3,
-    )
-
-    filter: FloatProperty(
-        name="Filter",
-        description="Amount to blend in the material’s diffuse color in raytraced "
-        "transparency (simulating absorption)",
-        min=0.0,
-        max=1.0,
-        default=0.0,
-        precision=3,
-    )
-
-    fresnel: FloatProperty(
-        name="Fresnel",
-        description="Power of Fresnel for transparency (Ray or ZTransp)",
-        min=0.0,
-        max=5.0,
-        soft_min=0.0,
-        soft_max=5.0,
-        default=0.0,
-        precision=3,
-    )
-
-    fresnel_factor: FloatProperty(
-        name="Blend",
-        description="Blending factor for Fresnel",
-        min=0.0,
-        max=5.0,
-        soft_min=0.0,
-        soft_max=5.0,
-        default=1.250,
-        precision=3,
-    )
-
-    gloss_factor: FloatProperty(
-        name="Amount",
-        description="The clarity of the refraction. "
-        "(values < 1.0 give diffuse, blurry refractions)",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-        precision=3,
-    )
-
-    gloss_samples: IntProperty(
-        name="Samples",
-        description="frequency of the noise sample used for blurry refractions",
-        min=0,
-        max=1024,
-        default=18,
-    )
-
-    gloss_threshold: FloatProperty(
-        name="Threshold",
-        description="Threshold for adaptive sampling (if a sample "
-        "contributes less than this amount [as a percentage], "
-        "sampling is stopped)",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=0.005,
-        precision=3,
-    )
-
-    ior: FloatProperty(
-        name="IOR",
-        description="Sets angular index of refraction for raytraced refraction",
-        min=-0.0,
-        max=10.0,
-        soft_min=0.25,
-        soft_max=4.0,
-        default=1.3,
-    )
-
-
-class MaterialRaytraceMirror(PropertyGroup):
-    """Declare reflection panel properties controllable in UI and translated to POV."""
-
-    bl_description = ("Raytraced reflection settings for the Material",)
-
-    use: BoolProperty(name="Mirror", description="Enable raytraced reflections", default=False)
-
-    depth: IntProperty(
-        name="Depth",
-        description="Maximum allowed number of light inter-reflections",
-        min=0,
-        max=32767,
-        default=2,
-    )
-
-    distance: FloatProperty(
-        name="Max Dist",
-        description="Maximum distance of reflected rays "
-        "(reflections further than this range "
-        "fade to sky color or material color)",
-        min=0.0,
-        max=100000.0,
-        soft_min=0.0,
-        soft_max=10000.0,
-        default=0.0,
-        precision=3,
-    )
-
-    fade_to: EnumProperty(
-        items=[
-            ("FADE_TO_SKY", "Fade to sky", ""),
-            ("FADE_TO_MATERIAL", "Fade to material color", ""),
-        ],
-        name="Fade-out Color",
-        description="The color that rays with no intersection within the "
-        "Max Distance take (material color can be best for "
-        "indoor scenes, sky color for outdoor)",
-        default="FADE_TO_SKY",
-    )
-
-    fresnel: FloatProperty(
-        name="Fresnel",
-        description="Power of Fresnel for mirror reflection",
-        min=0.0,
-        max=5.0,
-        soft_min=0.0,
-        soft_max=5.0,
-        default=0.0,
-        precision=3,
-    )
-
-    fresnel_factor: FloatProperty(
-        name="Blend",
-        description="Blending factor for Fresnel",
-        min=0.0,
-        max=5.0,
-        soft_min=0.0,
-        soft_max=5.0,
-        default=1.250,
-        precision=3,
-    )
-
-    gloss_anisotropic: FloatProperty(
-        name="Anisotropic",
-        description="The shape of the reflection, from 0.0 (circular) "
-        "to 1.0 (fully stretched along the tangent",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-        precision=3,
-    )
-
-    gloss_factor: FloatProperty(
-        name="Amount",
-        description="The shininess of the reflection  "
-        "(values < 1.0 give diffuse, blurry reflections)",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-        precision=3,
-    )
-
-    gloss_samples: IntProperty(
-        name="Noise",
-        description="Frequency of the noise pattern bumps averaged for blurry reflections",
-        min=0,
-        max=1024,
-        default=18,
-    )
-
-    gloss_threshold: FloatProperty(
-        name="Threshold",
-        description="Threshold for adaptive sampling (if a sample "
-        "contributes less than this amount [as a percentage], "
-        "sampling is stopped)",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=0.005,
-        precision=3,
-    )
-
-    mirror_color: FloatVectorProperty(
-        name="Mirror color",
-        description="Mirror color of the material",
-        precision=4,
-        step=0.01,
-        default=(1.0, 1.0, 1.0),
-        options={"ANIMATABLE"},
-        subtype="COLOR",
-    )
-
-    reflect_factor: FloatProperty(
-        name="Reflectivity",
-        description="Amount of mirror reflection for raytrace",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-        precision=3,
-    )
-
-
-class MaterialSubsurfaceScattering(PropertyGroup):
-    r"""Declare SSS/SSTL properties controllable in UI and translated to POV."""
-
-    bl_description = ("Subsurface scattering settings for the material",)
-
-    use: BoolProperty(
-        name="Subsurface Scattering",
-        description="Enable diffuse subsurface scatting " "effects in a material",
-        default=False,
-    )
-
-    back: FloatProperty(
-        name="Back",
-        description="Back scattering weight",
-        min=0.0,
-        max=10.0,
-        soft_min=0.0,
-        soft_max=10.0,
-        default=1.0,
-        precision=3,
-    )
-
-    color: FloatVectorProperty(
-        name="Scattering color",
-        description="Scattering color",
-        precision=4,
-        step=0.01,
-        default=(0.604, 0.604, 0.604),
-        options={"ANIMATABLE"},
-        subtype="COLOR",
-    )
-
-    color_factor: FloatProperty(
-        name="Color",
-        description="Blend factor for SSS colors",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-        precision=3,
-    )
-
-    error_threshold: FloatProperty(
-        name="Error",
-        description="Error tolerance (low values are slower and higher quality)",
-        default=0.050,
-        precision=3,
-    )
-
-    front: FloatProperty(
-        name="Front",
-        description="Front scattering weight",
-        min=0.0,
-        max=2.0,
-        soft_min=0.0,
-        soft_max=2.0,
-        default=1.0,
-        precision=3,
-    )
-
-    ior: FloatProperty(
-        name="IOR",
-        description="Index of refraction (higher values are denser)",
-        min=-0.0,
-        max=10.0,
-        soft_min=0.1,
-        soft_max=2.0,
-        default=1.3,
-    )
-
-    radius: FloatVectorProperty(
-        name="RGB Radius",
-        description="Mean red/green/blue scattering path length",
-        precision=4,
-        step=0.01,
-        min=0.001,
-        default=(1.0, 1.0, 1.0),
-        options={"ANIMATABLE"},
-    )
-
-    scale: FloatProperty(
-        name="Scale", description="Object scale factor", default=0.100, precision=3
-    )
-
-    texture_factor: FloatProperty(
-        name="Texture",
-        description="Texture scattering blend factor",
-        min=0.0,
-        max=1.0,
-        soft_min=0.0,
-        soft_max=1.0,
-        default=0.0,
-        precision=3,
-    )
-
-
-class MaterialStrandSettings(PropertyGroup):
-    """Declare strand properties controllable in UI and translated to POV."""
-
-    bl_description = ("Strand settings for the material",)
-
-    blend_distance: FloatProperty(
-        name="Distance",
-        description="Worldspace distance over which to blend in the surface normal",
-        min=0.0,
-        max=10.0,
-        soft_min=0.0,
-        soft_max=10.0,
-        default=0.0,
-        precision=3,
-    )
-
-    root_size: FloatProperty(
-        name="Root",
-        description="Start size of strands in pixels or Blender units",
-        min=0.25,
-        default=1.0,
-        precision=5,
-    )
-
-    shape: FloatProperty(
-        name="Shape",
-        description="Positive values make strands rounder, negative ones make strands spiky",
-        min=-0.9,
-        max=0.9,
-        default=0.0,
-        precision=3,
-    )
-
-    size_min: FloatProperty(
-        name="Minimum",
-        description="Minimum size of strands in pixels",
-        min=0.001,
-        max=10.0,
-        default=1.0,
-        precision=3,
-    )
-
-    tip_size: FloatProperty(
-        name="Tip",
-        description="End size of strands in pixels or Blender units",
-        min=0.0,
-        default=1.0,
-        precision=5,
-    )
-
-    use_blender_units: BoolProperty(
-        name="Blender Units",
-        description="Use Blender units for widths instead of pixels",
-        default=False,
-    )
-
-    use_surface_diffuse: BoolProperty(
-        name="Surface diffuse",
-        description="Make diffuse shading more similar to shading the surface",
-        default=False,
-    )
-
-    use_tangent_shading: BoolProperty(
-        name="Tangent Shading",
-        description="Use direction of strands as normal for tangent-shading",
-        default=True,
-    )
 
-    uv_layer: StringProperty(
-        name="UV Layer",
-        # icon="GROUP_UVS",
-        description="Name of UV map to override",
-        default="",
-    )
+# ------------------------------ End Old Blender Internal Props ------------------------------ #
 
-    width_fade: FloatProperty(
-        name="Width Fade",
-        description="Transparency along the width of the strand",
-        min=0.0,
-        max=2.0,
-        default=0.0,
-        precision=3,
-    )
 
-    # halo
+classes = (
+    RenderPovSettingsMaterial,
+)
 
-    # Halo settings for the material
-    # Type: MaterialHalo, (readonly, never None)
 
-    # ambient
-
-    # Amount of global ambient color the material receives
-    # Type: float in [0, 1], default 0.0
-
-    # darkness
-
-    # Minnaert darkness
-    # Type: float in [0, 2], default 0.0
-
-    # diffuse_color
-
-    # Diffuse color of the material
-    # Type: float array of 3 items in [0, inf], default (0.0, 0.0, 0.0)
-
-    # diffuse_fresnel
-
-    # Power of Fresnel
-    # Type: float in [0, 5], default 0.0
-
-    # diffuse_fresnel_factor
-
-    # Blending factor of Fresnel
-    # Type: float in [0, 5], default 0.0
-
-    # diffuse_intensity
-
-    # Amount of diffuse reflection
-    # Type: float in [0, 1], default 0.0
-
-    # diffuse_ramp
-
-    # Color ramp used to affect diffuse shading
-    # Type: ColorRamp, (readonly)
-
-    # diffuse_ramp_blend
-
-    # Blending method of the ramp and the diffuse color
-    # Type: enum in [‘MIX’, ‘ADD’, ‘MULTIPLY’, ‘SUBTRACT’, ‘SCREEN’, ‘DIVIDE’, ‘DIFFERENCE’, ‘DARKEN’, ‘LIGHTEN’, ‘OVERLAY’, ‘DODGE’, ‘BURN’, ‘HUE’, ‘SATURATION’, ‘VALUE’, ‘COLOR’, ‘SOFT_LIGHT’, ‘LINEAR_LIGHT’], default ‘MIX’
-
-    # diffuse_ramp_factor
-
-    # Blending factor (also uses alpha in Colorband)
-    # Type: float in [0, 1], default 0.0
-
-    # diffuse_ramp_input
-
-    # How the ramp maps on the surface
-    # Type: enum in [‘SHADER’, ‘ENERGY’, ‘NORMAL’, ‘RESULT’], default ‘SHADER’
-
-    # diffuse_shader
-
-    # LAMBERT Lambert, Use a Lambertian shader.
-    # OREN_NAYAR Oren-Nayar, Use an Oren-Nayar shader.
-    # TOON Toon, Use a toon shader.
-    # MINNAERT Minnaert, Use a Minnaert shader.
-    # FRESNEL Fresnel, Use a Fresnel shader.
-
-    # Type: enum in [‘LAMBERT’, ‘OREN_NAYAR’, ‘TOON’, ‘MINNAERT’, ‘FRESNEL’], default ‘LAMBERT’
-
-    # diffuse_toon_size
-
-    # Size of diffuse toon area
-    # Type: float in [0, 3.14], default 0.0
-
-    # diffuse_toon_smooth
-
-    # Smoothness of diffuse toon area
-    # Type: float in [0, 1], default 0.0
-
-    # emit
-
-    # Amount of light to emit
-    # Type: float in [0, inf], default 0.0
-
-    # game_settings
-
-    # Game material settings
-    # Type: MaterialGameSettings, (readonly, never None)
-
-    # halo
-
-    # Halo settings for the material
-    # Type: MaterialHalo, (readonly, never None)
-
-    # invert_z
-
-    # Render material’s faces with an inverted Z buffer (scanline only)
-    # Type: boolean, default False
-
-    # light_group
-
-    # Limit lighting to lamps in this Group
-    # Type: Group
-
-    # line_color
-
-    # Line color used for Freestyle line rendering
-    # Type: float array of 4 items in [0, inf], default (0.0, 0.0, 0.0, 0.0)
-
-    # line_priority
-
-    # The line color of a higher priority is used at material boundaries
-    # Type: int in [0, 32767], default 0
-
-    # mirror_color
-
-    # Mirror color of the material
-    # Type: float array of 3 items in [0, inf], default (0.0, 0.0, 0.0)
-
-    # node_tree
-
-    # Node tree for node based materials
-    # Type: NodeTree, (readonly)
-
-    # offset_z
-
-    # Give faces an artificial offset in the Z buffer for Z transparency
-    # Type: float in [-inf, inf], default 0.0
-
-    # paint_active_slot
-
-    # Index of active texture paint slot
-    # Type: int in [0, 32767], default 0
-
-    # paint_clone_slot
-
-    # Index of clone texture paint slot
-    # Type: int in [0, 32767], default 0
-
-    # pass_index
-
-    # Index number for the “Material Index” render pass
-    # Type: int in [0, 32767], default 0
-
-    # physics
-
-    # Game physics settings
-    # Type: MaterialPhysics, (readonly, never None)
-
-    # preview_render_type
-
-    # Type of preview render
-
-    # FLAT Flat, Flat XY plane.
-    # SPHERE Sphere, Sphere.
-    # CUBE Cube, Cube.
-    # MONKEY Monkey, Monkey.
-    # HAIR Hair, Hair strands.
-    # SPHERE_A World Sphere, Large sphere with sky.
-
-    # Type: enum in [‘FLAT’, ‘SPHERE’, ‘CUBE’, ‘MONKEY’, ‘HAIR’, ‘SPHERE_A’], default ‘FLAT’
-
-    # raytrace_mirror
-
-    # Raytraced reflection settings for the material
-    # Type: MaterialRaytraceMirror, (readonly, never None)
-
-    # raytrace_transparency
-
-    # Raytraced transparency settings for the material
-    # Type: MaterialRaytraceTransparency, (readonly, never None)
-
-    # roughness
-
-    # Oren-Nayar Roughness
-    # Type: float in [0, 3.14], default 0.0
-
-    # shadow_buffer_bias
-
-    # Factor to multiply shadow buffer bias with (0 is ignore)
-    # Type: float in [0, 10], default 0.0
-
-    # shadow_cast_alpha
-
-    # Shadow casting alpha, in use for Irregular and Deep shadow buffer
-    # Type: float in [0.001, 1], default 0.0
-
-    # shadow_only_type
-
-    # How to draw shadows
-
-    # SHADOW_ONLY_OLD Shadow and Distance, Old shadow only method.
-    # SHADOW_ONLY Shadow Only, Improved shadow only method.
-    # SHADOW_ONLY_SHADED Shadow and Shading, Improved shadow only method which also renders lightless areas as shadows.
-
-    # Type: enum in [‘SHADOW_ONLY_OLD’, ‘SHADOW_ONLY’, ‘SHADOW_ONLY_SHADED’], default ‘SHADOW_ONLY_OLD’
-
-    # shadow_ray_bias
-
-    # Shadow raytracing bias to prevent terminator problems on shadow boundary
-    # Type: float in [0, 0.25], default 0.0
-
-    # specular_color
-
-    # Specular color of the material
-    # Type: float array of 3 items in [0, inf], default (0.0, 0.0, 0.0)
-
-    # specular_hardness
-
-    # How hard (sharp) the specular reflection is
-    # Type: int in [1, 511], default 0
-
-    # specular_intensity
-
-    # How intense (bright) the specular reflection is
-    # Type: float in [0, 1], default 0.0
-
-    # specular_ior
-
-    # Specular index of refraction
-    # Type: float in [1, 10], default 0.0
-
-    # specular_ramp
-
-    # Color ramp used to affect specular shading
-    # Type: ColorRamp, (readonly)
-
-    # specular_ramp_blend
-
-    # Blending method of the ramp and the specular color
-    # Type: enum in [‘MIX’, ‘ADD’, ‘MULTIPLY’, ‘SUBTRACT’, ‘SCREEN’, ‘DIVIDE’, ‘DIFFERENCE’, ‘DARKEN’, ‘LIGHTEN’, ‘OVERLAY’, ‘DODGE’, ‘BURN’, ‘HUE’, ‘SATURATION’, ‘VALUE’, ‘COLOR’, ‘SOFT_LIGHT’, ‘LINEAR_LIGHT’], default ‘MIX’
-
-    # specular_ramp_factor
-
-    # Blending factor (also uses alpha in Colorband)
-    # Type: float in [0, 1], default 0.0
-
-    # specular_ramp_input
-
-    # How the ramp maps on the surface
-    # Type: enum in [‘SHADER’, ‘ENERGY’, ‘NORMAL’, ‘RESULT’], default ‘SHADER’
-    # specular_shader
-
-    # COOKTORR CookTorr, Use a Cook-Torrance shader.
-    # PHONG Phong, Use a Phong shader.
-    # BLINN Blinn, Use a Blinn shader.
-    # TOON Toon, Use a toon shader.
-    # WARDISO WardIso, Use a Ward anisotropic shader.
-
-    # Type: enum in [‘COOKTORR’, ‘PHONG’, ‘BLINN’, ‘TOON’, ‘WARDISO’], default ‘COOKTORR’
-
-    # specular_slope
-
-    # The standard deviation of surface slope
-    # Type: float in [0, 0.4], default 0.0
-
-    # specular_toon_size
-
-    # Size of specular toon area
-    # Type: float in [0, 1.53], default 0.0
-
-    # specular_toon_smooth
-
-    # Smoothness of specular toon area
-    # Type: float in [0, 1], default 0.0
-
-    # strand
-
-    # Strand settings for the material
-    # Type: MaterialStrand, (readonly, never None)
-
-    # subsurface_scattering
-
-    # Subsurface scattering settings for the material
-    # Type: MaterialSubsurfaceScattering, (readonly, never None)
-
-    # texture_paint_images
-
-    # Texture images used for texture painting
-    # Type: bpy_prop_collection of Image, (readonly)
-
-    # texture_paint_slots
-
-    # Texture slots defining the mapping and influence of textures
-    # Type: bpy_prop_collection of TexPaintSlot, (readonly)
-
-    # texture_slots
-
-    # Texture slots defining the mapping and influence of textures
-    # Type: MaterialTextureSlots bpy_prop_collection of MaterialTextureSlot, (readonly)
-
-    # translucency
-
-    # Amount of diffuse shading on the back side
-    # Type: float in [0, 1], default 0.0
-
-    # transparency_method
-
-    # Method to use for rendering transparency
-
-    # MASK Mask, Mask the background.
-    # Z_TRANSPARENCY Z Transparency, Use alpha buffer for transparent faces.
-    # RAYTRACE Raytrace, Use raytracing for transparent refraction rendering.
-
-    # Type: enum in [‘MASK’, ‘Z_TRANSPARENCY’, ‘RAYTRACE’], default ‘MASK’
-
-    # type
-
-    # Material type defining how the object is rendered
-
-    # SURFACE Surface, Render object as a surface.
-    # WIRE Wire, Render the edges of faces as wires (not supported in raytracing).
-    # VOLUME Volume, Render object as a volume.
-    # HALO Halo, Render object as halo particles.
-
-    # Type: enum in [‘SURFACE’, ‘WIRE’, ‘VOLUME’, ‘HALO’], default ‘SURFACE’
-
-    # use_cast_approximate
-
-    # Allow this material to cast shadows when using approximate ambient occlusion
-    # Type: boolean, default False
-
-    # use_cast_buffer_shadows
-
-    # Allow this material to cast shadows from shadow buffer lamps
-    # Type: boolean, default False
-
-    # use_cast_shadows
-
-    # Allow this material to cast shadows
-    # Type: boolean, default False
-
-    # use_cast_shadows_only
-
-    # Make objects with this material appear invisible (not rendered), only casting shadows
-    # Type: boolean, default False
-
-    # use_cubic
-
-    # Use cubic interpolation for diffuse values, for smoother transitions
-    # Type: boolean, default False
-
-    # use_diffuse_ramp
-
-    # Toggle diffuse ramp operations
-    # Type: boolean, default False
-
-    # use_face_texture
-
-    # Replace the object’s base color with color from UV map image textures
-    # Type: boolean, default False
-
-    # use_face_texture_alpha
-
-    # Replace the object’s base alpha value with alpha from UV map image textures
-    # Type: boolean, default False
-
-    # use_full_oversampling
-
-    # Force this material to render full shading/textures for all anti-aliasing samples
-    # Type: boolean, default False
-
-    # use_light_group_exclusive
-
-    # Material uses the light group exclusively - these lamps are excluded from other scene lighting
-    # Type: boolean, default False
-
-    # use_light_group_local
-
-    # When linked in, material uses local light group with the same name
-    # Type: boolean, default False
-
-    # use_mist
-
-    # Use mist with this material (in world settings)
-    # Type: boolean, default False
-
-    # use_nodes
-
-    # Use shader nodes to render the material
-    # Type: boolean, default False
-
-    # use_object_color
-
-    # Modulate the result with a per-object color
-    # Type: boolean, default False
-
-    # use_only_shadow
-
-    # Render shadows as the material’s alpha value, making the material transparent except for shadowed areas
-    # Type: boolean, default False
-
-    # use_ray_shadow_bias
-
-    # Prevent raytraced shadow errors on surfaces with smooth shaded normals (terminator problem)
-    # Type: boolean, default False
-
-    # use_raytrace
-
-    # Include this material and geometry that uses it in raytracing calculations
-    # Type: boolean, default False
-
-    # use_shadeless
-
-    # Make this material insensitive to light or shadow
-    # Type: boolean, default False
-
-    # use_shadows
-
-    # Allow this material to receive shadows
-    # Type: boolean, default False
-
-    # use_sky
-
-    # Render this material with zero alpha, with sky background in place (scanline only)
-    # Type: boolean, default False
-
-    # use_specular_ramp
-
-    # Toggle specular ramp operations
-    # Type: boolean, default False
-
-    # use_tangent_shading
-
-    # Use the material’s tangent vector instead of the normal for shading - for anisotropic shading effects
-    # Type: boolean, default False
-
-    # use_textures
-
-    # Enable/Disable each texture
-    # Type: boolean array of 18 items, default (False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False)
-
-    # use_transparency
-
-    # Render material as transparent
-    # Type: boolean, default False
-
-    # use_transparent_shadows
-
-    # Allow this object to receive transparent shadows cast through other objects
-    # Type: boolean, default False
-
-    # use_uv_project
-
-    # Use to ensure UV interpolation is correct for camera projections (use with UV project modifier)
-    # Type: boolean, default False
-
-    # use_vertex_color_light
-
-    # Add vertex colors as additional lighting
-    # Type: boolean, default False
-
-    # use_vertex_color_paint
-
-    # Replace object base color with vertex colors (multiply with ‘texture face’ face assigned textures)
-    # Type: boolean, default False
-
-    # volume
-
-    # Volume settings for the material
-    # Type: MaterialVolume, (readonly, never None)
-    """
-    (mat.type in {'SURFACE', 'WIRE', 'VOLUME'})
-     "use_transparency")
-
-
-
-    mat.use_transparency and mat.transparency_method == 'Z_TRANSPARENCY'
-
-
-
-
-            col.prop(mat, "use_raytrace")
-            col.prop(mat, "use_full_oversampling")
-
-            sub.prop(mat, "use_sky")
-
-
-            col.prop(mat, "use_cast_shadows", text="Cast")
-            col.prop(mat, "use_cast_shadows_only", text="Cast Only")
-            col.prop(mat, "use_cast_buffer_shadows")
-
-            sub.active = mat.use_cast_buffer_shadows
-            sub.prop(mat, "shadow_cast_alpha", text="Casting Alpha")
-            col.prop(mat, "use_cast_approximate")
-
-
-
-            col.prop(mat, "diffuse_color", text="")
-
-            sub.active = (not mat.use_shadeless)
-
-            sub.prop(mat, "diffuse_intensity", text="Intensity")
-
-
-            col.prop(mat, "diffuse_shader", text="")
-            col.prop(mat, "use_diffuse_ramp", text="Ramp")
-
-
-            if mat.diffuse_shader == 'OREN_NAYAR':
-                col.prop(mat, "roughness")
-            elif mat.diffuse_shader == 'MINNAERT':
-                col.prop(mat, "darkness")
-            elif mat.diffuse_shader == 'TOON':
-
-                row.prop(mat, "diffuse_toon_size", text="Size")
-                row.prop(mat, "diffuse_toon_smooth", text="Smooth")
-            elif mat.diffuse_shader == 'FRESNEL':
-
-                row.prop(mat, "diffuse_fresnel", text="Fresnel")
-                row.prop(mat, "diffuse_fresnel_factor", text="Factor")
-
-            if mat.use_diffuse_ramp:
-
-                col.template_color_ramp(mat, "diffuse_ramp", expand=True)
-
-
-
-                row.prop(mat, "diffuse_ramp_input", text="Input")
-                row.prop(mat, "diffuse_ramp_blend", text="Blend")
-
-                col.prop(mat, "diffuse_ramp_factor", text="Factor")
-
-
-
-
-            col.prop(mat, "specular_color", text="")
-            col.prop(mat, "specular_intensity", text="Intensity")
-
-            col.prop(mat, "specular_shader", text="")
-            col.prop(mat, "use_specular_ramp", text="Ramp")
-
-            if mat.specular_shader in {'COOKTORR', 'PHONG'}:
-                col.prop(mat, "specular_hardness", text="Hardness")
-            elif mat.specular_shader == 'BLINN':
-
-                row.prop(mat, "specular_hardness", text="Hardness")
-                row.prop(mat, "specular_ior", text="IOR")
-            elif mat.specular_shader == 'WARDISO':
-                col.prop(mat, "specular_slope", text="Slope")
-            elif mat.specular_shader == 'TOON':
-
-                row.prop(mat, "specular_toon_size", text="Size")
-                row.prop(mat, "specular_toon_smooth", text="Smooth")
-
-            if mat.use_specular_ramp:
-                layout.separator()
-                layout.template_color_ramp(mat, "specular_ramp", expand=True)
-                layout.separator()
-
-                row = layout.row()
-                row.prop(mat, "specular_ramp_input", text="Input")
-                row.prop(mat, "specular_ramp_blend", text="Blend")
-
-                layout.prop(mat, "specular_ramp_factor", text="Factor")
-
-
-    class MATERIAL_PT_shading(MaterialButtonsPanel, Panel):
-        bl_label = "Shading"
-        COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'}
-
-        @classmethod
-        def poll(cls, context):
-            mat = context.material
-            engine = context.scene.render.engine
-            return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES)
-
-        def draw(self, context):
-            layout = self.layout
-
-            mat = active_node_mat(context.material)
-
-            if mat.type in {'SURFACE', 'WIRE'}:
-                split = layout.split()
-
-                col = split.column()
-                sub = col.column()
-                sub.active = not mat.use_shadeless
-                sub.prop(mat, "emit")
-                sub.prop(mat, "ambient")
-                sub = col.column()
-                sub.prop(mat, "translucency")
-
-                col = split.column()
-                col.prop(mat, "use_shadeless")
-                sub = col.column()
-                sub.active = not mat.use_shadeless
-                sub.prop(mat, "use_tangent_shading")
-                sub.prop(mat, "use_cubic")
-
-
-    class MATERIAL_PT_transp(MaterialButtonsPanel, Panel):
-        bl_label = "Transparency"
-        COMPAT_ENGINES = {'BLENDER_RENDER'}
-
-        @classmethod
-        def poll(cls, context):
-            mat = context.material
-            engine = context.scene.render.engine
-            return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES)
-
-        def draw_header(self, context):
-            mat = context.material
-
-            if simple_material(mat):
-                self.layout.prop(mat, "use_transparency", text="")
-
-        def draw(self, context):
-            layout = self.layout
-
-            base_mat = context.material
-            mat = active_node_mat(context.material)
-            rayt = mat.raytrace_transparency
-
-            if simple_material(base_mat):
-                row = layout.row()
-                row.active = mat.use_transparency
-                row.prop(mat, "transparency_method", expand=True)
-
-            split = layout.split()
-            split.active = base_mat.use_transparency
-
-            col = split.column()
-            col.prop(mat, "alpha")
-            row = col.row()
-            row.active = (base_mat.transparency_method != 'MASK') and (not mat.use_shadeless)
-            row.prop(mat, "specular_alpha", text="Specular")
-
-            col = split.column()
-            col.active = (not mat.use_shadeless)
-            col.prop(rayt, "fresnel")
-            sub = col.column()
-            sub.active = (rayt.fresnel > 0.0)
-            sub.prop(rayt, "fresnel_factor", text="Blend")
-
-            if base_mat.transparency_method == 'RAYTRACE':
-                layout.separator()
-                split = layout.split()
-                split.active = base_mat.use_transparency
-
-                col = split.column()
-                col.prop(rayt, "ior")
-                col.prop(rayt, "filter")
-                col.prop(rayt, "falloff")
-                col.prop(rayt, "depth_max")
-                col.prop(rayt, "depth")
-
-                col = split.column()
-                col.label(text="Gloss:")
-                col.prop(rayt, "gloss_factor", text="Amount")
-                sub = col.column()
-                sub.active = rayt.gloss_factor < 1.0
-                sub.prop(rayt, "gloss_threshold", text="Threshold")
-                sub.prop(rayt, "gloss_samples", text="Samples")
-
-
-    class MATERIAL_PT_mirror(MaterialButtonsPanel, Panel):
-        bl_label = "Mirror"
-        bl_options = {'DEFAULT_CLOSED'}
-        COMPAT_ENGINES = {'BLENDER_RENDER'}
-
-        @classmethod
-        def poll(cls, context):
-            mat = context.material
-            engine = context.scene.render.engine
-            return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES)
-
-        def draw_header(self, context):
-            raym = active_node_mat(context.material).raytrace_mirror
-
-            self.layout.prop(raym, "use", text="")
-
-        def draw(self, context):
-            layout = self.layout
-
-            mat = active_node_mat(context.material)
-            raym = mat.raytrace_mirror
-
-            layout.active = raym.use
-
-            split = layout.split()
-
-            col = split.column()
-            col.prop(raym, "reflect_factor")
-            col.prop(mat, "mirror_color", text="")
-
-            col = split.column()
-            col.prop(raym, "fresnel")
-            sub = col.column()
-            sub.active = (raym.fresnel > 0.0)
-            sub.prop(raym, "fresnel_factor", text="Blend")
-
-            split = layout.split()
-
-            col = split.column()
-            col.separator()
-            col.prop(raym, "depth")
-            col.prop(raym, "distance", text="Max Dist")
-            col.separator()
-            sub = col.split(percentage=0.4)
-            sub.active = (raym.distance > 0.0)
-            sub.label(text="Fade To:")
-            sub.prop(raym, "fade_to", text="")
-
-            col = split.column()
-            col.label(text="Gloss:")
-            col.prop(raym, "gloss_factor", text="Amount")
-            sub = col.column()
-            sub.active = (raym.gloss_factor < 1.0)
-            sub.prop(raym, "gloss_threshold", text="Threshold")
-            sub.prop(raym, "gloss_samples", text="Samples")
-            sub.prop(raym, "gloss_anisotropic", text="Anisotropic")
-
-
-    class MATERIAL_PT_sss(MaterialButtonsPanel, Panel):
-        bl_label = "Subsurface Scattering"
-        bl_options = {'DEFAULT_CLOSED'}
-        COMPAT_ENGINES = {'BLENDER_RENDER'}
-
-        @classmethod
-        def poll(cls, context):
-            mat = context.material
-            engine = context.scene.render.engine
-            return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES)
-
-        def draw_header(self, context):
-            mat = active_node_mat(context.material)
-            sss = mat.subsurface_scattering
-
-            self.layout.active = (not mat.use_shadeless)
-            self.layout.prop(sss, "use", text="")
-
-        def draw(self, context):
-            layout = self.layout
-
-            mat = active_node_mat(context.material)
-            sss = mat.subsurface_scattering
-
-            layout.active = (sss.use) and (not mat.use_shadeless)
-
-            row = layout.row().split()
-            sub = row.row(align=True).split(align=True, percentage=0.75)
-            sub.menu("MATERIAL_MT_sss_presets", text=bpy.types.MATERIAL_MT_sss_presets.bl_label)
-            sub.operator("material.sss_preset_add", text="", icon='ZOOMIN')
-            sub.operator("material.sss_preset_add", text="", icon='ZOOMOUT').remove_active = True
-
-            split = layout.split()
-
-            col = split.column()
-            col.prop(sss, "ior")
-            col.prop(sss, "scale")
-            col.prop(sss, "color", text="")
-            col.prop(sss, "radius", text="RGB Radius", expand=True)
-
-            col = split.column()
-            sub = col.column(align=True)
-            sub.label(text="Blend:")
-            sub.prop(sss, "color_factor", text="Color")
-            sub.prop(sss, "texture_factor", text="Texture")
-            sub.label(text="Scattering Weight:")
-            sub.prop(sss, "front")
-            sub.prop(sss, "back")
-            col.separator()
-            col.prop(sss, "error_threshold", text="Error")
-
-
-    class MATERIAL_PT_halo(MaterialButtonsPanel, Panel):
-        bl_label = "Halo"
-        COMPAT_ENGINES = {'BLENDER_RENDER'}
-
-        @classmethod
-        def poll(cls, context):
-            mat = context.material
-            engine = context.scene.render.engine
-            return mat and (mat.type == 'HALO') and (engine in cls.COMPAT_ENGINES)
-
-        def draw(self, context):
-            layout = self.layout
-
-            mat = context.material  # don't use node material
-            halo = mat.halo
-
-            def number_but(layout, toggle, number, name, color):
-                row = layout.row(align=True)
-                row.prop(halo, toggle, text="")
-                sub = row.column(align=True)
-                sub.active = getattr(halo, toggle)
-                sub.prop(halo, number, text=name, translate=False)
-                if not color == "":
-                    sub.prop(mat, color, text="")
-
-            split = layout.split()
-
-            col = split.column()
-            col.prop(mat, "alpha")
-            col.prop(mat, "diffuse_color", text="")
-            col.prop(halo, "seed")
-
-            col = split.column()
-            col.prop(halo, "size")
-            col.prop(halo, "hardness")
-            col.prop(halo, "add")
-
-            layout.label(text="Options:")
-
-            split = layout.split()
-            col = split.column()
-            col.prop(halo, "use_texture")
-            col.prop(halo, "use_vertex_normal")
-            col.prop(halo, "use_extreme_alpha")
-            col.prop(halo, "use_shaded")
-            col.prop(halo, "use_soft")
-
-            col = split.column()
-            number_but(col, "use_ring", "ring_count", iface_("Rings"), "mirror_color")
-            number_but(col, "use_lines", "line_count", iface_("Lines"), "specular_color")
-            number_but(col, "use_star", "star_tip_count", iface_("Star Tips"), "")
-
-
-    class MATERIAL_PT_flare(MaterialButtonsPanel, Panel):
-        bl_label = "Flare"
-        COMPAT_ENGINES = {'BLENDER_RENDER'}
-
-        @classmethod
-        def poll(cls, context):
-            mat = context.material
-            engine = context.scene.render.engine
-            return mat and (mat.type == 'HALO') and (engine in cls.COMPAT_ENGINES)
-
-        def draw_header(self, context):
-            halo = context.material.halo
-
-            self.layout.prop(halo, "use_flare_mode", text="")
-
-        def draw(self, context):
-            layout = self.layout
-
-            mat = context.material  # don't use node material
-            halo = mat.halo
-
-            layout.active = halo.use_flare_mode
-
-            split = layout.split()
-
-            col = split.column()
-            col.prop(halo, "flare_size", text="Size")
-            col.prop(halo, "flare_boost", text="Boost")
-            col.prop(halo, "flare_seed", text="Seed")
-
-            col = split.column()
-            col.prop(halo, "flare_subflare_count", text="Subflares")
-            col.prop(halo, "flare_subflare_size", text="Subsize")
-
-    """
-
-
-# ------------------------------ End Old Blender Internal Props ------------------------------ #
-
-
-classes = (
-    RenderPovSettingsMaterial,
-    MaterialRaytraceTransparency,
-    MaterialRaytraceMirror,
-    MaterialSubsurfaceScattering,
-    MaterialStrandSettings,
-)
-
-
-def register():
-    for cls in classes:
-        register_class(cls)
+def register():
+    for cls in classes:
+        register_class(cls)
 
     bpy.types.Material.pov = PointerProperty(type=RenderPovSettingsMaterial)
-    bpy.types.Material.pov_raytrace_transparency = PointerProperty(
-        type=MaterialRaytraceTransparency
-    )
-    bpy.types.Material.pov_subsurface_scattering = PointerProperty(
-        type=MaterialSubsurfaceScattering
-    )
-    bpy.types.Material.strand = PointerProperty(type=MaterialStrandSettings)
-    bpy.types.Material.pov_raytrace_mirror = PointerProperty(type=MaterialRaytraceMirror)
 
 
 def unregister():
-    del bpy.types.Material.pov_subsurface_scattering
-    del bpy.types.Material.strand
-    del bpy.types.Material.pov_raytrace_mirror
-    del bpy.types.Material.pov_raytrace_transparency
     del bpy.types.Material.pov
 
     for cls in reversed(classes):
diff --git a/render_povray/shading_ray_properties.py b/render_povray/shading_ray_properties.py
new file mode 100644
index 000000000..76e26f0e2
--- /dev/null
+++ b/render_povray/shading_ray_properties.py
@@ -0,0 +1,374 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+"""Declare shading properties exported to POV textures."""
+import bpy
+from bpy.utils import register_class, unregister_class
+from bpy.types import PropertyGroup
+from bpy.props import (
+    FloatVectorProperty,
+    StringProperty,
+    BoolProperty,
+    IntProperty,
+    FloatProperty,
+    EnumProperty,
+    PointerProperty,
+)
+
+
+class MaterialRaytraceTransparency(PropertyGroup):
+    """Declare transparency panel properties controllable in UI and translated to POV."""
+
+    depth: IntProperty(
+        name="Depth",
+        description="Maximum allowed number of light inter-refractions",
+        min=0,
+        max=32767,
+        default=2,
+    )
+
+    depth_max: FloatProperty(
+        name="Depth",
+        description="Maximum depth for light to travel through the "
+        "transparent material before becoming fully filtered (0.0 is disabled)",
+        min=0,
+        max=100,
+        default=0.0,
+    )
+
+    falloff: FloatProperty(
+        name="Falloff",
+        description="Falloff power for transmissivity filter effect (1.0 is linear)",
+        min=0.1,
+        max=10.0,
+        default=1.0,
+        precision=3,
+    )
+
+    filter: FloatProperty(
+        name="Filter",
+        description="Amount to blend in the material’s diffuse color in raytraced "
+        "transparency (simulating absorption)",
+        min=0.0,
+        max=1.0,
+        default=0.0,
+        precision=3,
+    )
+
+    fresnel: FloatProperty(
+        name="Fresnel",
+        description="Power of Fresnel for transparency (Ray or ZTransp)",
+        min=0.0,
+        max=5.0,
+        soft_min=0.0,
+        soft_max=5.0,
+        default=0.0,
+        precision=3,
+    )
+
+    fresnel_factor: FloatProperty(
+        name="Blend",
+        description="Blending factor for Fresnel",
+        min=0.0,
+        max=5.0,
+        soft_min=0.0,
+        soft_max=5.0,
+        default=1.250,
+        precision=3,
+    )
+
+    gloss_factor: FloatProperty(
+        name="Amount",
+        description="The clarity of the refraction. "
+        "(values < 1.0 give diffuse, blurry refractions)",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=1.0,
+        precision=3,
+    )
+
+    gloss_samples: IntProperty(
+        name="Samples",
+        description="frequency of the noise sample used for blurry refractions",
+        min=0,
+        max=1024,
+        default=18,
+    )
+
+    gloss_threshold: FloatProperty(
+        name="Threshold",
+        description="Threshold for adaptive sampling (if a sample "
+        "contributes less than this amount [as a percentage], "
+        "sampling is stopped)",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=0.005,
+        precision=3,
+    )
+
+    ior: FloatProperty(
+        name="IOR",
+        description="Sets angular index of refraction for raytraced refraction",
+        min=-0.0,
+        max=10.0,
+        soft_min=0.25,
+        soft_max=4.0,
+        default=1.3,
+    )
+
+
+class MaterialRaytraceMirror(PropertyGroup):
+    """Declare reflection panel properties controllable in UI and translated to POV."""
+
+    bl_description = ("Raytraced reflection settings for the Material",)
+
+    use: BoolProperty(name="Mirror", description="Enable raytraced reflections", default=False)
+
+    depth: IntProperty(
+        name="Depth",
+        description="Maximum allowed number of light inter-reflections",
+        min=0,
+        max=32767,
+        default=2,
+    )
+
+    distance: FloatProperty(
+        name="Max Dist",
+        description="Maximum distance of reflected rays "
+        "(reflections further than this range "
+        "fade to sky color or material color)",
+        min=0.0,
+        max=100000.0,
+        soft_min=0.0,
+        soft_max=10000.0,
+        default=0.0,
+        precision=3,
+    )
+
+    fade_to: EnumProperty(
+        items=[
+            ("FADE_TO_SKY", "Fade to sky", ""),
+            ("FADE_TO_MATERIAL", "Fade to material color", ""),
+        ],
+        name="Fade-out Color",
+        description="The color that rays with no intersection within the "
+        "Max Distance take (material color can be best for "
+        "indoor scenes, sky color for outdoor)",
+        default="FADE_TO_SKY",
+    )
+
+    fresnel: FloatProperty(
+        name="Fresnel",
+        description="Power of Fresnel for mirror reflection",
+        min=0.0,
+        max=5.0,
+        soft_min=0.0,
+        soft_max=5.0,
+        default=0.0,
+        precision=3,
+    )
+
+    fresnel_factor: FloatProperty(
+        name="Blend",
+        description="Blending factor for Fresnel",
+        min=0.0,
+        max=5.0,
+        soft_min=0.0,
+        soft_max=5.0,
+        default=1.250,
+        precision=3,
+    )
+
+    gloss_anisotropic: FloatProperty(
+        name="Anisotropic",
+        description="The shape of the reflection, from 0.0 (circular) "
+        "to 1.0 (fully stretched along the tangent",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=1.0,
+        precision=3,
+    )
+
+    gloss_factor: FloatProperty(
+        name="Amount",
+        description="The shininess of the reflection  "
+        "(values < 1.0 give diffuse, blurry reflections)",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=1.0,
+        precision=3,
+    )
+
+    gloss_samples: IntProperty(
+        name="Noise",
+        description="Frequency of the noise pattern bumps averaged for blurry reflections",
+        min=0,
+        max=1024,
+        default=18,
+    )
+
+    gloss_threshold: FloatProperty(
+        name="Threshold",
+        description="Threshold for adaptive sampling (if a sample "
+        "contributes less than this amount [as a percentage], "
+        "sampling is stopped)",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=0.005,
+        precision=3,
+    )
+
+    mirror_color: FloatVectorProperty(
+        name="Mirror color",
+        description="Mirror color of the material",
+        precision=4,
+        step=0.01,
+        default=(1.0, 1.0, 1.0),
+        options={"ANIMATABLE"},
+        subtype="COLOR",
+    )
+
+    reflect_factor: FloatProperty(
+        name="Reflectivity",
+        description="Amount of mirror reflection for raytrace",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=1.0,
+        precision=3,
+    )
+
+
+class MaterialSubsurfaceScattering(PropertyGroup):
+    """Declare SSS/SSTL properties controllable in UI and translated to POV."""
+
+    bl_description = ("Subsurface scattering settings for the material",)
+
+    use: BoolProperty(
+        name="Subsurface Scattering",
+        description="Enable diffuse subsurface scatting " "effects in a material",
+        default=False,
+    )
+
+    back: FloatProperty(
+        name="Back",
+        description="Back scattering weight",
+        min=0.0,
+        max=10.0,
+        soft_min=0.0,
+        soft_max=10.0,
+        default=1.0,
+        precision=3,
+    )
+
+    color: FloatVectorProperty(
+        name="Scattering color",
+        description="Scattering color",
+        precision=4,
+        step=0.01,
+        default=(0.604, 0.604, 0.604),
+        options={"ANIMATABLE"},
+        subtype="COLOR",
+    )
+
+    color_factor: FloatProperty(
+        name="Color",
+        description="Blend factor for SSS colors",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=1.0,
+        precision=3,
+    )
+
+    error_threshold: FloatProperty(
+        name="Error",
+        description="Error tolerance (low values are slower and higher quality)",
+        default=0.050,
+        precision=3,
+    )
+
+    front: FloatProperty(
+        name="Front",
+        description="Front scattering weight",
+        min=0.0,
+        max=2.0,
+        soft_min=0.0,
+        soft_max=2.0,
+        default=1.0,
+        precision=3,
+    )
+
+    ior: FloatProperty(
+        name="IOR",
+        description="Index of refraction (higher values are denser)",
+        min=-0.0,
+        max=10.0,
+        soft_min=0.1,
+        soft_max=2.0,
+        default=1.3,
+    )
+
+    radius: FloatVectorProperty(
+        name="RGB Radius",
+        description="Mean red/green/blue scattering path length",
+        precision=4,
+        step=0.01,
+        min=0.001,
+        default=(1.0, 1.0, 1.0),
+        options={"ANIMATABLE"},
+    )
+
+    scale: FloatProperty(
+        name="Scale", description="Object scale factor", default=0.100, precision=3
+    )
+
+    texture_factor: FloatProperty(
+        name="Texture",
+        description="Texture scattering blend factor",
+        min=0.0,
+        max=1.0,
+        soft_min=0.0,
+        soft_max=1.0,
+        default=0.0,
+        precision=3,
+    )
+
+classes = (
+    MaterialRaytraceTransparency,
+    MaterialRaytraceMirror,
+    MaterialSubsurfaceScattering,
+)
+
+def register():
+    for cls in classes:
+        register_class(cls)
+
+    bpy.types.Material.pov_raytrace_transparency = PointerProperty(
+        type=MaterialRaytraceTransparency
+    )
+    bpy.types.Material.pov_subsurface_scattering = PointerProperty(
+        type=MaterialSubsurfaceScattering
+    )
+    bpy.types.Material.pov_raytrace_mirror = PointerProperty(type=MaterialRaytraceMirror)
+
+
+def unregister():
+    del bpy.types.Material.pov_subsurface_scattering
+    del bpy.types.Material.pov_raytrace_mirror
+    del bpy.types.Material.pov_raytrace_transparency
+
+    for cls in reversed(classes):
+        unregister_class(cls)
diff --git a/render_povray/texturing.py b/render_povray/texturing.py
index dadc5e5c4..55e72565b 100755
--- a/render_povray/texturing.py
+++ b/render_povray/texturing.py
@@ -6,26 +6,28 @@
 import os
 import bpy
 
+local_material_names = []
+material_finish = None
+
 
 def write_texture_influence(
-    using_uberpov,
+    file,
     mater,
     material_names_dictionary,
-    local_material_names,
-    path_image,
-    exported_lights_count,
     image_format,
     img_map,
     img_map_transforms,
     tab_write,
     comments,
-    string_strip_hyphen,
-    safety,
     col,
     preview_dir,
     unpacked_images,
 ):
     """Translate Blender texture influences to various POV texture tricks and write to pov file."""
+
+    from .scenography import path_image, exported_lights_count
+    from .render import string_strip_hyphen, safety, using_uberpov
+
     material_finish = material_names_dictionary[mater.name]
     trans = 1.0 - mater.pov.alpha if mater.pov.use_transparency else 0.0
     if (mater.pov.specular_color.s == 0.0) or (mater.pov.diffuse_shader == "MINNAERT"):
@@ -46,126 +48,129 @@ def write_texture_influence(
     texture_alpha = ""
     # procedural_flag=False
     tmpidx = -1
-    for t in mater.pov_texture_slots:
+    used_texture_slots = (tesl for tesl in mater.pov_texture_slots if tesl.use)
+    # and (bpy.data.textures[mater.pov_texture_slots[tmpidx-1].texture] is not None)
+    for t in used_texture_slots:
 
         tmpidx += 1
         # index = mater.pov.active_texture_index
         slot = mater.pov_texture_slots[tmpidx]  # [index]
         povtex = slot.texture  # slot.name
         tex = bpy.data.textures[povtex]
-
-        if t and (t.use and (tex is not None)):
-            # 'NONE' ('NONE' type texture is different from no texture covered above)
-            if tex.type == "NONE" and tex.pov.tex_pattern_type == "emulator":
-                continue  # move to next slot
-
-            # Implicit else-if (as not skipped by previous "continue")
-            if tex.type != "IMAGE" and tex.type != "NONE":
-                # PROCEDURAL TEXTURE
-                image_filename = "PAT_%s" % string_strip_hyphen(bpy.path.clean_name(tex.name))
-                if image_filename:
-                    if t.use_map_color_diffuse:
-                        texture_dif = image_filename
-                        # colvalue = t.default_value  # UNUSED
-                        t_dif = t
-                        if t_dif.texture.pov.tex_gamma_enable:
-                            img_gamma = " gamma %.3g " % t_dif.texture.pov.tex_gamma_value
-                    if t.use_map_specular or t.use_map_raymir:
-                        texture_spec = image_filename
-                        # colvalue = t.default_value  # UNUSED
-                        t_spec = t
-                    if t.use_map_normal:
-                        texture_norm = image_filename
-                        # colvalue = t.normal_factor/10 # UNUSED
-                        # textNormName=tex.image.name + ".normal"
-                        # was the above used? --MR
-                        t_nor = t
-                    if t.use_map_alpha:
-                        texture_alpha = image_filename
-                        # colvalue = t.alpha_factor * 10.0  # UNUSED
-                        # textDispName=tex.image.name + ".displ"
-                        # was the above used? --MR
-                        t_alpha = t
-            # RASTER IMAGE
-            elif tex.type == "IMAGE" and tex.image and tex.pov.tex_pattern_type == "emulator":
-                # NOT A PROCEDURAL TEXTURE
-                # PACKED
-                if tex.image.packed_file:
-                    orig_image_filename = tex.image.filepath_raw
-                    unpackedfilename = os.path.join(
-                        preview_dir,
-                        ("unpacked_img_" + (string_strip_hyphen(bpy.path.clean_name(tex.name)))),
-                    )
-                    if not os.path.exists(unpackedfilename):
-                        # record which images that were newly copied and can be safely
-                        # cleaned up
-                        unpacked_images.append(unpackedfilename)
-                    tex.image.filepath_raw = unpackedfilename
-                    tex.image.save()
-                    image_filename = unpackedfilename.replace("\\", "/")
-                    # .replace("\\","/") to get only forward slashes as it's what POV prefers,
-                    # even on windows
-                    tex.image.filepath_raw = orig_image_filename
-                # FILE
-                else:
-                    image_filename = path_image(tex.image)
-                # IMAGE SEQUENCE BEGINS
-                if (
-                    image_filename
-                    and bpy.data.images[tex.image.name].source == "SEQUENCE"
-                ):
-                    korvaa = "." + str(tex.image_user.frame_offset + 1).zfill(3) + "."
-                    image_filename = image_filename.replace(".001.", korvaa)
-                    print(" seq debug ")
-                    print(image_filename)
-                # IMAGE SEQUENCE ENDS
-                img_gamma = ""
-                if image_filename:
-                    texdata = bpy.data.textures[t.texture]
-                    if t.use_map_color_diffuse:
-                        texture_dif = image_filename
-                        # colvalue = t.default_value  # UNUSED
-                        t_dif = t
-                        print(texdata)
-                        if texdata.pov.tex_gamma_enable:
-                            img_gamma = " gamma %.3g " % t_dif.texture.pov.tex_gamma_value
-                    if t.use_map_specular or t.use_map_raymir:
-                        texture_spec = image_filename
-                        # colvalue = t.default_value  # UNUSED
-                        t_spec = t
-                    if t.use_map_normal:
-                        texture_norm = image_filename
-                        # colvalue = t.normal_factor/10  # UNUSED
-                        # textNormName=tex.image.name + ".normal"
-                        # was the above used? --MR
-                        t_nor = t
-                    if t.use_map_alpha:
-                        texture_alpha = image_filename
-                        # colvalue = t.alpha_factor * 10.0  # UNUSED
-                        # textDispName=tex.image.name + ".displ"
-                        # was the above used? --MR
-                        t_alpha = t
+        if tex is None:
+            continue  # move to next slot
+        # 'NONE' ('NONE' type texture is different from no texture covered above)
+        if tex.type == "NONE" and tex.pov.tex_pattern_type == "emulator":
+            continue  # move to next slot
+
+        # Implicit else-if (as not skipped by previous "continue")
+        if tex.type not in ("IMAGE","NONE"):
+            # PROCEDURAL TEXTURE
+            image_filename = "PAT_%s" % string_strip_hyphen(bpy.path.clean_name(tex.name))
+            if image_filename:
+                if t.use_map_color_diffuse:
+                    texture_dif = image_filename
+                    # colvalue = t.default_value  # UNUSED
+                    t_dif = t
+                    if t_dif.texture.pov.tex_gamma_enable:
+                        img_gamma = " gamma %.3g " % t_dif.texture.pov.tex_gamma_value
+                if t.use_map_specular or t.use_map_raymir:
+                    texture_spec = image_filename
+                    # colvalue = t.default_value  # UNUSED
+                    t_spec = t
+                if t.use_map_normal:
+                    texture_norm = image_filename
+                    # colvalue = t.normal_factor/10 # UNUSED
+                    # textNormName=tex.image.name + ".normal"
+                    # was the above used? --MR
+                    t_nor = t
+                if t.use_map_alpha:
+                    texture_alpha = image_filename
+                    # colvalue = t.alpha_factor * 10.0  # UNUSED
+                    # textDispName=tex.image.name + ".displ"
+                    # was the above used? --MR
+                    t_alpha = t
+        # RASTER IMAGE
+        elif tex.type == "IMAGE" and tex.image and tex.pov.tex_pattern_type == "emulator":
+            # NOT A PROCEDURAL TEXTURE
+            # PACKED
+            if tex.image.packed_file:
+                orig_image_filename = tex.image.filepath_raw
+                unpackedfilename = os.path.join(
+                    preview_dir,
+                    ("unpacked_img_" + (string_strip_hyphen(bpy.path.clean_name(tex.name)))),
+                )
+                if not os.path.exists(unpackedfilename):
+                    # record which images that were newly copied and can be safely
+                    # cleaned up
+                    unpacked_images.append(unpackedfilename)
+                tex.image.filepath_raw = unpackedfilename
+                tex.image.save()
+                image_filename = unpackedfilename.replace("\\", "/")
+                # .replace("\\","/") to get only forward slashes as it's what POV prefers,
+                # even on windows
+                tex.image.filepath_raw = orig_image_filename
+            # FILE
+            else:
+                image_filename = path_image(tex.image)
+            # IMAGE SEQUENCE BEGINS
+            if image_filename and bpy.data.images[tex.image.name].source == "SEQUENCE":
+                korvaa = "." + str(tex.image_user.frame_offset + 1).zfill(3) + "."
+                image_filename = image_filename.replace(".001.", korvaa)
+                print(" seq debug ")
+                print(image_filename)
+            # IMAGE SEQUENCE ENDS
+            img_gamma = ""
+            if image_filename:
+                texdata = bpy.data.textures[t.texture]
+                if t.use_map_color_diffuse:
+                    texture_dif = image_filename
+                    # colvalue = t.default_value  # UNUSED
+                    t_dif = t
+                    print(texdata)
+                    if texdata.pov.tex_gamma_enable:
+                        img_gamma = " gamma %.3g " % t_dif.texture.pov.tex_gamma_value
+                if t.use_map_specular or t.use_map_raymir:
+                    texture_spec = image_filename
+                    # colvalue = t.default_value  # UNUSED
+                    t_spec = t
+                if t.use_map_normal:
+                    texture_norm = image_filename
+                    # colvalue = t.normal_factor/10  # UNUSED
+                    # textNormName=tex.image.name + ".normal"
+                    # was the above used? --MR
+                    t_nor = t
+                if t.use_map_alpha:
+                    texture_alpha = image_filename
+                    # colvalue = t.alpha_factor * 10.0  # UNUSED
+                    # textDispName=tex.image.name + ".displ"
+                    # was the above used? --MR
+                    t_alpha = t
 
     # -----------------------------------------------------------------------------
 
-    tab_write("\n")
+    tab_write(file, "\n")
     # THIS AREA NEEDS TO LEAVE THE TEXTURE OPEN UNTIL ALL MAPS ARE WRITTEN DOWN.
 
     current_material_name = string_strip_hyphen(material_names_dictionary[mater.name])
+    global local_material_names
     local_material_names.append(current_material_name)
-    tab_write("\n#declare MAT_%s = \ntexture{\n" % current_material_name)
+    tab_write(file, "\n#declare MAT_%s = \ntexture{\n" % current_material_name)
     # -----------------------------------------------------------------------------
 
-    if mater.pov.replacement_text != "":
-        tab_write("%s\n" % mater.pov.replacement_text)
+    if mater.pov.replacement_text:
+        tab_write(file, "%s\n" % mater.pov.replacement_text)
     # -----------------------------------------------------------------------------
     # XXX TODO: replace by new POV MINNAERT rather than aoi
     if mater.pov.diffuse_shader == "MINNAERT":
-        tab_write("\n")
-        tab_write("aoi\n")
-        tab_write("texture_map {\n")
-        tab_write("[%.3g finish {diffuse %.3g}]\n" % (mater.darkness / 2.0, 2.0 - mater.darkness))
-        tab_write("[%.3g\n" % (1.0 - (mater.darkness / 2.0)))
+        tab_write(file, "\n")
+        tab_write(file, "aoi\n")
+        tab_write(file, "texture_map {\n")
+        tab_write(
+            file, "[%.3g finish {diffuse %.3g}]\n" % (mater.pov.darkness / 2.0,
+                                                      2.0 - mater.pov.darkness)
+        )
+        tab_write(file, "[%.3g\n" % (1.0 - (mater.pov.darkness / 2.0)))
 
     if mater.pov.diffuse_shader == "FRESNEL":
         # For FRESNEL diffuse in POV, we'll layer slope patterned textures
@@ -174,55 +179,61 @@ def write_texture_influence(
 
         c = 1
         while c <= exported_lights_count:
-            tab_write("slope { lampTarget%s }\n" % c)
-            tab_write("texture_map {\n")
+            tab_write(file, "slope { lampTarget%s }\n" % c)
+            tab_write(file, "texture_map {\n")
             # Diffuse Fresnel value and factor go up to five,
             # other kind of values needed: used the number 5 below to remap
             tab_write(
+                file,
                 "[%.3g finish {diffuse %.3g}]\n"
                 % (
-                    (5.0 - mater.diffuse_fresnel) / 5,
-                    (mater.diffuse_intensity * ((5.0 - mater.diffuse_fresnel_factor) / 5)),
-                )
+                    (5.0 - mater.pov.diffuse_fresnel) / 5,
+                    (mater.pov.diffuse_intensity * ((5.0 - mater.pov.diffuse_fresnel_factor) / 5)),
+                ),
             )
             tab_write(
-                "[%.3g\n" % ((mater.diffuse_fresnel_factor / 5) * (mater.diffuse_fresnel / 5.0))
+                file,
+                "[%.3g\n" % ((mater.pov.diffuse_fresnel_factor / 5) * (mater.pov.diffuse_fresnel / 5.0)),
             )
             c += 1
 
     # if shader is a 'FRESNEL' or 'MINNAERT': slope pigment pattern or aoi
     # and texture map above, the rest below as one of its entry
 
-    if texture_spec != "" or texture_alpha != "":
-        if texture_spec != "":
-            # tab_write("\n")
-            tab_write("pigment_pattern {\n")
+    if texture_spec or texture_alpha:
+        if texture_spec:
+            # tab_write(file, "\n")
+            tab_write(file, "pigment_pattern {\n")
 
             mapping_spec = img_map_transforms(t_spec)
             if texture_spec and texture_spec.startswith("PAT_"):
-                tab_write("function{f%s(x,y,z).grey}\n" % texture_spec)
+                tab_write(file, "function{f%s(x,y,z).grey}\n" % texture_spec)
             else:
 
                 tab_write(
+                    file,
                     'uv_mapping image_map{%s "%s" %s}\n'
-                    % (image_format(texture_spec), texture_spec, img_map(t_spec))
+                    % (image_format(texture_spec), texture_spec, img_map(t_spec)),
                 )
-            tab_write("%s\n" % mapping_spec)
-            tab_write("}\n")
-            tab_write("texture_map {\n")
-            tab_write("[0 \n")
+            tab_write(file, "%s\n" % mapping_spec)
+            tab_write(file, "}\n")
+            tab_write(file, "texture_map {\n")
+            tab_write(file, "[0 \n")
 
-        if texture_dif == "":
-            if texture_alpha != "":
-                tab_write("\n")
+        if not texture_dif:
+            if texture_alpha:
+                tab_write(file, "\n")
 
                 mapping_alpha = img_map_transforms(t_alpha)
 
                 if texture_alpha and texture_alpha.startswith("PAT_"):
-                    tab_write("function{f%s(x,y,z).transmit}%s\n" % (texture_alpha, mapping_alpha))
+                    tab_write(
+                        file, "function{f%s(x,y,z).transmit}%s\n" % (texture_alpha, mapping_alpha)
+                    )
                 else:
 
                     tab_write(
+                        file,
                         "pigment {pigment_pattern {uv_mapping image_map"
                         '{%s "%s" %s}%s'
                         % (
@@ -230,110 +241,118 @@ def write_texture_influence(
                             texture_alpha,
                             img_map(t_alpha),
                             mapping_alpha,
-                        )
+                        ),
                     )
-                tab_write("}\n")
-                tab_write("pigment_map {\n")
-                tab_write("[0 color rgbft<0,0,0,1,1>]\n")
+                tab_write(file, "}\n")
+                tab_write(file, "pigment_map {\n")
+                tab_write(file, "[0 color rgbft<0,0,0,1,1>]\n")
                 tab_write(
+                    file,
                     "[1 color rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>]\n"
-                    % (col[0], col[1], col[2], pov_filter, trans)
+                    % (col[0], col[1], col[2], pov_filter, trans),
                 )
-                tab_write("}\n")
-                tab_write("}\n")
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
 
             else:
 
                 tab_write(
+                    file,
                     "pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>}\n"
-                    % (col[0], col[1], col[2], pov_filter, trans)
+                    % (col[0], col[1], col[2], pov_filter, trans),
                 )
 
         else:
             mapping_dif = img_map_transforms(t_dif)
 
-            if texture_alpha != "":
+            if texture_alpha:
                 mapping_alpha = img_map_transforms(t_alpha)
 
-                tab_write("pigment {\n")
-                tab_write("pigment_pattern {\n")
+                tab_write(file, "pigment {\n")
+                tab_write(file, "pigment_pattern {\n")
                 if texture_alpha and texture_alpha.startswith("PAT_"):
-                    tab_write("function{f%s(x,y,z).transmit}%s\n" % (texture_alpha, mapping_alpha))
+                    tab_write(
+                        file, "function{f%s(x,y,z).transmit}%s\n" % (texture_alpha, mapping_alpha)
+                    )
                 else:
                     tab_write(
+                        file,
                         'uv_mapping image_map{%s "%s" %s}%s}\n'
                         % (
                             image_format(texture_alpha),
                             texture_alpha,
                             img_map(t_alpha),
                             mapping_alpha,
-                        )
+                        ),
                     )
-                tab_write("pigment_map {\n")
-                tab_write("[0 color rgbft<0,0,0,1,1>]\n")
+                tab_write(file, "pigment_map {\n")
+                tab_write(file, "[0 color rgbft<0,0,0,1,1>]\n")
                 # if texture_alpha and texture_alpha.startswith("PAT_"):
-                # tab_write("[1 pigment{%s}]\n" %texture_dif)
+                # tab_write(file, "[1 pigment{%s}]\n" %texture_dif)
                 if texture_dif:
                     if not texture_dif.startswith("PAT_"):
                         tab_write(
+                            file,
                             '[1 uv_mapping image_map {%s "%s" %s} %s]\n'
                             % (
                                 image_format(texture_dif),
                                 texture_dif,
                                 (img_gamma + img_map(t_dif)),
                                 mapping_dif,
-                            )
+                            ),
                         )
                     elif texture_dif.startswith("PAT_"):
-                        tab_write("[1 %s]\n" % texture_dif)
-                tab_write("}\n")
-                tab_write("}\n")
+                        tab_write(file, "[1 %s]\n" % texture_dif)
+                tab_write(file, "}\n")
+                tab_write(file, "}\n")
                 if texture_alpha and texture_alpha.startswith("PAT_"):
-                    tab_write("}\n")
+                    tab_write(file, "}\n")
 
             else:
                 if texture_dif and texture_dif.startswith("PAT_"):
-                    tab_write("pigment{%s}\n" % texture_dif)
+                    tab_write(file, "pigment{%s}\n" % texture_dif)
                 else:
                     tab_write(
+                        file,
                         'pigment {uv_mapping image_map {%s "%s" %s}%s}\n'
                         % (
                             image_format(texture_dif),
                             texture_dif,
                             (img_gamma + img_map(t_dif)),
                             mapping_dif,
-                        )
+                        ),
                     )
 
                     # scale 1 rotate y*0
                     # imageMap = ("{image_map {%s \"%s\" %s }\n" % \
                     #            (image_format(textures),textures,img_map(t_dif)))
-                    # tab_write("uv_mapping pigment %s} %s finish {%s}\n" % \
+                    # tab_write(file, "uv_mapping pigment %s} %s finish {%s}\n" % \
                     #         (imageMap,mapping,safety(material_finish)))
-                    # tab_write("pigment {uv_mapping image_map {%s \"%s\" %s}%s} " \
+                    # tab_write(file, "pigment {uv_mapping image_map {%s \"%s\" %s}%s} " \
                     #         "finish {%s}\n" % \
                     #         (image_format(texture_dif), texture_dif, img_map(t_dif),
                     #          mapping_dif, safety(material_finish)))
-        if texture_spec != "":
+        if texture_spec:
             # ref_level_bound 1 is no specular
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=1)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=1)))
 
         else:
             # ref_level_bound 2 is translated spec
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=2)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=2)))
 
-        if texture_norm != "":
+        if texture_norm:
             # scale 1 rotate y*0
 
             mapping_normal = img_map_transforms(t_nor)
 
             if texture_norm and texture_norm.startswith("PAT_"):
                 tab_write(
+                    file,
                     "normal{function{f%s(x,y,z).grey} bump_size %.4g %s}\n"
-                    % (texture_norm, (-t_nor.normal_factor * 9.5), mapping_normal)
+                    % (texture_norm, (-t_nor.normal_factor * 9.5), mapping_normal),
                 )
             else:
-                tab_write("normal {\n")
+                tab_write(file, "normal {\n")
                 # XXX TODO: fix and propagate the micro normals reflection blur below
                 #  to non textured materials
                 if (
@@ -341,83 +360,94 @@ def write_texture_influence(
                     and mater.pov_raytrace_mirror.gloss_factor < 1.0
                     and not using_uberpov
                 ):
-                    tab_write("average\n")
-                    tab_write("normal_map{\n")
+                    tab_write(file, "average\n")
+                    tab_write(file, "normal_map{\n")
                     # 0.5 for entries below means a 50 percent mix
                     # between the micro normal and user bump map
                     # order seems indifferent as commutative
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (10 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.1]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.15]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.2]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.25]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.3]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.35]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.4]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.45]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
                     tab_write(
+                        file,
                         "[0.025 bumps %.4g scale 0.1*%.4g phase 0.5]\n"
                         % (
                             (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                             (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                        )
+                        ),
                     )  # micronormals blurring
-                    tab_write("[1.0 ")  # Proceed with user bump...
+                    tab_write(file, "[1.0 ")  # Proceed with user bump...
                 tab_write(
+                    file,
                     "uv_mapping bump_map "
                     '{%s "%s" %s  bump_size %.4g }%s'
                     % (
@@ -426,7 +456,7 @@ def write_texture_influence(
                         img_map(t_nor),
                         (-t_nor.normal_factor * 9.5),
                         mapping_normal,
-                    )
+                    ),
                 )
                 # ...Then close its last entry and the the normal_map itself
                 if (
@@ -434,132 +464,148 @@ def write_texture_influence(
                     and mater.pov_raytrace_mirror.gloss_factor < 1.0
                     and not using_uberpov
                 ):
-                    tab_write(r"]}}"+"\n")
+                    tab_write(file, r"]}}" + "\n")
                 else:
-                    tab_write(r"}"+"\n")
-    if texture_spec != "":
-        tab_write("]\n")
+                    tab_write(file, r"}" + "\n")
+    if texture_spec:
+        tab_write(file, "]\n")
         # -------- Second index for mapping specular max value -------- #
-        tab_write("[1 \n")
+        tab_write(file, "[1 \n")
 
-    if texture_dif == "" and mater.pov.replacement_text == "":
-        if texture_alpha != "":
+    if not texture_dif and not mater.pov.replacement_text:
+        if texture_alpha:
             mapping_alpha = img_map_transforms(t_alpha)
 
             if texture_alpha and texture_alpha.startswith("PAT_"):
-                tab_write("function{f%s(x,y,z).transmit %s}\n" % (texture_alpha, mapping_alpha))
+                tab_write(
+                    file, "function{f%s(x,y,z).transmit %s}\n" % (texture_alpha, mapping_alpha)
+                )
             else:
                 tab_write(
+                    file,
                     "pigment {pigment_pattern {uv_mapping image_map"
                     '{%s "%s" %s}%s}\n'
-                    % (image_format(texture_alpha), texture_alpha, img_map(t_alpha), mapping_alpha)
+                    % (image_format(texture_alpha), texture_alpha, img_map(t_alpha), mapping_alpha),
                 )
-            tab_write("pigment_map {\n")
-            tab_write("[0 color rgbft<0,0,0,1,1>]\n")
+            tab_write(file, "pigment_map {\n")
+            tab_write(file, "[0 color rgbft<0,0,0,1,1>]\n")
             tab_write(
+                file,
                 "[1 color rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>]\n"
-                % (col[0], col[1], col[2], pov_filter, trans)
+                % (col[0], col[1], col[2], pov_filter, trans),
             )
-            tab_write("}\n")
-            tab_write("}\n")
+            tab_write(file, "}\n")
+            tab_write(file, "}\n")
 
         else:
             tab_write(
+                file,
                 "pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>}\n"
-                % (col[0], col[1], col[2], pov_filter, trans)
+                % (col[0], col[1], col[2], pov_filter, trans),
             )
 
-        if texture_spec != "":
+        if texture_spec:
             # ref_level_bound 3 is full specular
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=3)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=3)))
 
             if (
                 mater.pov_raytrace_mirror.use
-                and mater.pov_raytrace_mirror.gloss_factor < 1.0
+                and mater.pov_raytrace_mirror.gloss_factor < 1
                 and not using_uberpov
             ):
-                tab_write("normal {\n")
-                tab_write("average\n")
-                tab_write("normal_map{\n")
+                tab_write(file, "normal {\n")
+                tab_write(file, "average\n")
+                tab_write(file, "normal_map{\n")
                 # 0.5 for entries below means a 50 percent mix
                 # between the micro normal and user bump map
                 # order seems indifferent as commutative
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (10 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.1]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.15]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.2]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.25]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.3]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.35]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.4]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.45]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.5]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
             # XXX IF USER BUMP_MAP
-            if texture_norm != "":
+            if texture_norm:
                 tab_write(
-                    "[1.0 "
+                    file, "[1.0 "
                 )  # Blurry reflection or not Proceed with user bump in either case...
                 tab_write(
+                    file,
                     "uv_mapping bump_map "
                     '{%s "%s" %s  bump_size %.4g }%s\n'
                     % (
@@ -568,104 +614,110 @@ def write_texture_influence(
                         img_map(t_nor),
                         (-t_nor.normal_factor * 9.5),
                         mapping_normal,
-                    )
+                    ),
                 )
             # ...Then close the normal_map itself if blurry reflection
             if (
                 mater.pov_raytrace_mirror.use
-                and mater.pov_raytrace_mirror.gloss_factor < 1.0
+                and mater.pov_raytrace_mirror.gloss_factor < 1
                 and not using_uberpov
             ):
-                tab_write("]\n}}\n")
+                tab_write(file, "]\n}}\n")
             else:
-                tab_write("}\n")
+                tab_write(file, "}\n")
         elif colored_specular_found:
             # ref_level_bound 1 is no specular
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=1)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=1)))
 
         else:
             # ref_level_bound 2 is translated specular
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=2)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=2)))
 
-    elif mater.pov.replacement_text == "":
+    elif not mater.pov.replacement_text:
         mapping_dif = img_map_transforms(t_dif)
 
-        if texture_alpha != "":
+        if texture_alpha:
 
             mapping_alpha = img_map_transforms(t_alpha)
 
             if texture_alpha and texture_alpha.startswith("PAT_"):
                 tab_write(
+                    file,
                     "pigment{pigment_pattern {function{f%s(x,y,z).transmit}%s}\n"
-                    % (texture_alpha, mapping_alpha)
+                    % (texture_alpha, mapping_alpha),
                 )
             else:
                 tab_write(
+                    file,
                     "pigment {pigment_pattern {uv_mapping image_map"
                     '{%s "%s" %s}%s}\n'
-                    % (image_format(texture_alpha), texture_alpha, img_map(t_alpha), mapping_alpha)
+                    % (image_format(texture_alpha), texture_alpha, img_map(t_alpha), mapping_alpha),
                 )
-            tab_write("pigment_map {\n")
-            tab_write("[0 color rgbft<0,0,0,1,1>]\n")
+            tab_write(file, "pigment_map {\n")
+            tab_write(file, "[0 color rgbft<0,0,0,1,1>]\n")
             if texture_alpha and texture_alpha.startswith("PAT_"):
-                tab_write("[1 function{f%s(x,y,z).transmit}%s]\n" % (texture_alpha, mapping_alpha))
+                tab_write(
+                    file, "[1 function{f%s(x,y,z).transmit}%s]\n" % (texture_alpha, mapping_alpha)
+                )
             elif texture_dif and not texture_dif.startswith("PAT_"):
                 tab_write(
+                    file,
                     '[1 uv_mapping image_map {%s "%s" %s} %s]\n'
                     % (
                         image_format(texture_dif),
                         texture_dif,
                         (img_map(t_dif) + img_gamma),
                         mapping_dif,
-                    )
+                    ),
                 )
             elif texture_dif and texture_dif.startswith("PAT_"):
-                tab_write("[1 %s %s]\n" % (texture_dif, mapping_dif))
-            tab_write("}\n")
-            tab_write("}\n")
+                tab_write(file, "[1 %s %s]\n" % (texture_dif, mapping_dif))
+            tab_write(file, "}\n")
+            tab_write(file, "}\n")
 
         else:
             if texture_dif and texture_dif.startswith("PAT_"):
-                tab_write("pigment{%s %s}\n" % (texture_dif, mapping_dif))
+                tab_write(file, "pigment{%s %s}\n" % (texture_dif, mapping_dif))
             else:
-                tab_write("pigment {\n")
-                tab_write("uv_mapping image_map {\n")
-                # tab_write("%s \"%s\" %s}%s\n" % \
+                tab_write(file, "pigment {\n")
+                tab_write(file, "uv_mapping image_map {\n")
+                # tab_write(file, "%s \"%s\" %s}%s\n" % \
                 #         (image_format(texture_dif), texture_dif,
                 #         (img_gamma + img_map(t_dif)),mapping_dif))
-                tab_write('%s "%s" \n' % (image_format(texture_dif), texture_dif))
-                tab_write("%s\n" % (img_gamma + img_map(t_dif)))
-                tab_write("}\n")
-                tab_write("%s\n" % mapping_dif)
-                tab_write("}\n")
+                tab_write(file, '%s "%s" \n' % (image_format(texture_dif), texture_dif))
+                tab_write(file, "%s\n" % (img_gamma + img_map(t_dif)))
+                tab_write(file, "}\n")
+                tab_write(file, "%s\n" % mapping_dif)
+                tab_write(file, "}\n")
 
-        if texture_spec != "":
+        if texture_spec:
             # ref_level_bound 3 is full specular
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=3)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=3)))
         else:
             # ref_level_bound 2 is translated specular
-            tab_write("finish {%s}\n" % (safety(material_finish, ref_level_bound=2)))
+            tab_write(file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=2)))
 
         # scale 1 rotate y*0
         # imageMap = ("{image_map {%s \"%s\" %s }" % \
         #            (image_format(textures), textures,img_map(t_dif)))
-        # tab_write("\n\t\t\tuv_mapping pigment %s} %s finish {%s}" % \
+        # tab_write(file, "\n\t\t\tuv_mapping pigment %s} %s finish {%s}" % \
         #           (imageMap, mapping, safety(material_finish)))
-        # tab_write("\n\t\t\tpigment {uv_mapping image_map " \
+        # tab_write(file, "\n\t\t\tpigment {uv_mapping image_map " \
         #           "{%s \"%s\" %s}%s} finish {%s}" % \
         #           (image_format(texture_dif), texture_dif,img_map(t_dif),
         #            mapping_dif, safety(material_finish)))
-    if texture_norm != "" and mater.pov.replacement_text == "":
+    if texture_norm and not mater.pov.replacement_text:
 
         mapping_normal = img_map_transforms(t_nor)
 
         if texture_norm and texture_norm.startswith("PAT_"):
             tab_write(
+                file,
                 "normal{function{f%s(x,y,z).grey} bump_size %.4g %s}\n"
-                % (texture_norm, (-t_nor.normal_factor * 9.5), mapping_normal)
+                % (texture_norm, (-t_nor.normal_factor * 9.5), mapping_normal),
             )
         else:
-            tab_write("normal {\n")
+            tab_write(file, "normal {\n")
             # XXX TODO: fix and propagate the micro normals reflection blur below
             #  to non textured materials
             if (
@@ -673,85 +725,96 @@ def write_texture_influence(
                 and mater.pov_raytrace_mirror.gloss_factor < 1.0
                 and not using_uberpov
             ):
-                tab_write("average\n")
-                tab_write("normal_map{\n")
+                tab_write(file, "average\n")
+                tab_write(file, "normal_map{\n")
                 # 0.5 for entries below means a 50 percent mix
                 # between the micro normal and user bump map
                 # order seems indifferent as commutative
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (10 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.1]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.15]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.2]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.25]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.3]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.35]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.4]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.45]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
+                    file,
                     "[0.025 bumps %.4g scale 0.1*%.4g phase 0.5]\n"
                     % (
                         (10 / (mater.pov_raytrace_mirror.gloss_factor + 0.01)),
                         (1 / (mater.pov_raytrace_mirror.gloss_samples + 0.001)),
-                    )
+                    ),
                 )  # micronormals blurring
                 tab_write(
-                    "[1.0 "
+                    file, "[1.0 "
                 )  # Blurry reflection or not Proceed with user bump in either case...
             tab_write(
+                file,
                 "uv_mapping bump_map "
                 '{%s "%s" %s  bump_size %.4g }%s\n'
                 % (
@@ -760,38 +823,38 @@ def write_texture_influence(
                     img_map(t_nor),
                     (-t_nor.normal_factor * 9.5),
                     mapping_normal,
-                )
+                ),
             )
             # ...Then close the normal_map itself if blurry reflection
             if (
                 mater.pov_raytrace_mirror.use
-                and mater.pov_raytrace_mirror.gloss_factor < 1.0
+                and mater.pov_raytrace_mirror.gloss_factor < 1
                 and not using_uberpov
             ):
-                tab_write("]}}\n")
+                tab_write(file, "]}}\n")
             else:
-                tab_write("}\n")
-    if texture_spec != "" and mater.pov.replacement_text == "":
-        tab_write("]\n")
+                tab_write(file, "}\n")
+    if texture_spec and not mater.pov.replacement_text:
+        tab_write(file, "]\n")
 
-        tab_write("}\n")
+        tab_write(file, "}\n")
 
     # End of slope/ior texture_map
-    if mater.pov.diffuse_shader == "MINNAERT" and mater.pov.replacement_text == "":
-        tab_write("]\n")
-        tab_write("}\n")
-    if mater.pov.diffuse_shader == "FRESNEL" and mater.pov.replacement_text == "":
+    if mater.pov.diffuse_shader == "MINNAERT" and not mater.pov.replacement_text:
+        tab_write(file, "]\n")
+        tab_write(file, "}\n")
+    if mater.pov.diffuse_shader == "FRESNEL" and not mater.pov.replacement_text:
         c = 1
         while c <= exported_lights_count:
-            tab_write("]\n")
-            tab_write("}\n")
+            tab_write(file, "]\n")
+            tab_write(file, "}\n")
             c += 1
 
     # Close first layer of POV "texture" (Blender material)
-    tab_write("}\n")
+    tab_write(file, "}\n")
 
     colored_specular_found = bool(
-        (mater.pov.specular_color.s > 0.0) and (mater.pov.diffuse_shader != "MINNAERT")
+        (mater.pov.specular_color.s > 0) and (mater.pov.diffuse_shader != "MINNAERT")
     )
 
     # Write another layered texture using invisible diffuse and metallic trick
@@ -814,32 +877,32 @@ def write_texture_influence(
         )
     if colored_specular_found and not special_texture_found:
         if comments:
-            tab_write("  // colored highlights with a stransparent metallic layer\n")
+            tab_write(file, "  // colored highlights with a stransparent metallic layer\n")
         else:
-            tab_write("\n")
+            tab_write(file, "\n")
 
-        tab_write("texture {\n")
+        tab_write(file, "texture {\n")
         tab_write(
+            file,
             "pigment {rgbft<%.3g, %.3g, %.3g, 0, 1>}\n"
             % (
                 mater.pov.specular_color[0],
                 mater.pov.specular_color[1],
                 mater.pov.specular_color[2],
-            )
+            ),
         )
         tab_write(
-            "finish {%s}\n" % (safety(material_finish, ref_level_bound=2))
+            file, "finish {%s}\n" % (safety(material_finish, ref_level_bound=2))
         )  # ref_level_bound 2 is translated spec
 
         texture_norm = ""
         for t in mater.pov_texture_slots:
 
-            if t and tex.pov.tex_pattern_type != "emulator":
+            if tex.pov.tex_pattern_type != "emulator":
                 # PROCEDURAL TEXTURE
                 image_filename = string_strip_hyphen(bpy.path.clean_name(tex.name))
             if (
-                t
-                and tex.type == "IMAGE"
+                tex.type == "IMAGE"
                 and t.use
                 and tex.image
                 and tex.pov.tex_pattern_type == "emulator"
@@ -855,12 +918,14 @@ def write_texture_influence(
                     t_nor = t
                     if procedural_flag:
                         tab_write(
+                            file,
                             "normal{function"
                             "{f%s(x,y,z).grey} bump_size %.4g}\n"
-                            % (texture_norm, (-t_nor.normal_factor * 9.5))
+                            % (texture_norm, (-t_nor.normal_factor * 9.5)),
                         )
                     else:
                         tab_write(
+                            file,
                             "normal {uv_mapping bump_map "
                             '{%s "%s" %s  bump_size %.4g }%s}\n'
                             % (
@@ -869,7 +934,7 @@ def write_texture_influence(
                                 img_map(t_nor),
                                 (-t_nor.normal_factor * 9.5),
                                 mapping_normal,
-                            )
+                            ),
                         )
 
-        tab_write("}\n")  # THEN IT CAN CLOSE LAST LAYER OF TEXTURE
+        tab_write(file, "}\n")  # THEN IT CAN CLOSE LAST LAYER OF TEXTURE
diff --git a/render_povray/texturing_gui.py b/render_povray/texturing_gui.py
index 007eda24c..5346eaffc 100755
--- a/render_povray/texturing_gui.py
+++ b/render_povray/texturing_gui.py
@@ -34,17 +34,17 @@ from bl_ui import properties_texture
 for member in dir(properties_texture):
     subclass = getattr(properties_texture, member)
     if hasattr(subclass, "COMPAT_ENGINES"):
-        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+        subclass.COMPAT_ENGINES.add("POVRAY_RENDER")
 del properties_texture
 
 
 class TextureButtonsPanel:
     """Use this class to define buttons from the texture tab properties."""
 
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     bl_context = "texture"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -57,19 +57,19 @@ class TEXTURE_MT_POV_specials(Menu):
     """Use this class to define pov texture slot operations buttons."""
 
     bl_label = "Texture Specials"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
 
-        layout.operator("texture.slot_copy", icon='COPYDOWN')
-        layout.operator("texture.slot_paste", icon='PASTEDOWN')
+        layout.operator("texture.slot_copy", icon="COPYDOWN")
+        layout.operator("texture.slot_paste", icon="PASTEDOWN")
 
 
 class WORLD_TEXTURE_SLOTS_UL_POV_layerlist(UIList):
     """Use this class to show pov texture slots list."""
 
-    index: bpy.props.IntProperty(name='index')
+    index: bpy.props.IntProperty(name="index")
     # should active_propname be index or..?
 
     def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -84,18 +84,18 @@ class WORLD_TEXTURE_SLOTS_UL_POV_layerlist(UIList):
         slot = item
         # ma = slot.name
         # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
-        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+        if self.layout_type in {"DEFAULT", "COMPACT"}:
             # You should always start your row layout by a label (icon + text), or a non-embossed text field,
             # this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
             # We use icon_value of label, as our given icon is an integer value, not an enum ID.
             # Note "data" names should never be translated!
             if slot:
-                layout.prop(slot, "texture", text="", emboss=False, icon='TEXTURE')
+                layout.prop(slot, "texture", text="", emboss=False, icon="TEXTURE")
             else:
                 layout.label(text="New", translate=False, icon_value=icon)
         # 'GRID' layout type should be as compact as possible (typically a single icon!).
-        elif self.layout_type in {'GRID'}:
-            layout.alignment = 'CENTER'
+        elif self.layout_type in {"GRID"}:
+            layout.alignment = "CENTER"
             layout.label(text="", icon_value=icon)
 
 
@@ -103,7 +103,7 @@ class MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist(UIList):
     """Use this class to show pov texture slots list."""
 
     #    texture_slots:
-    index: bpy.props.IntProperty(name='index')
+    index: bpy.props.IntProperty(name="index")
     # foo  = random prop
 
     def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
@@ -111,18 +111,18 @@ class MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist(UIList):
         slot = item
         # ma = slot.name
         # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
-        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+        if self.layout_type in {"DEFAULT", "COMPACT"}:
             # You should always start your row layout by a label (icon + text), or a non-embossed text field,
             # this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
             # We use icon_value of label, as our given icon is an integer value, not an enum ID.
             # Note "data" names should never be translated!
             if slot:
-                layout.prop(slot, "texture", text="", emboss=False, icon='TEXTURE')
+                layout.prop(slot, "texture", text="", emboss=False, icon="TEXTURE")
             else:
                 layout.label(text="New", translate=False, icon_value=icon)
         # 'GRID' layout type should be as compact as possible (typically a single icon!).
-        elif self.layout_type in {'GRID'}:
-            layout.alignment = 'CENTER'
+        elif self.layout_type in {"GRID"}:
+            layout.alignment = "CENTER"
             layout.label(text="", icon_value=icon)
 
 
@@ -131,8 +131,8 @@ class TEXTURE_PT_context(TextureButtonsPanel, Panel):
 
     bl_label = ""
     bl_context = "texture"
-    bl_options = {'HIDE_HEADER'}
-    COMPAT_ENGINES = {'POVRAY_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+    bl_options = {"HIDE_HEADER"}
+    COMPAT_ENGINES = {"POVRAY_RENDER", "BLENDER_EEVEE", "BLENDER_WORKBENCH"}
     # register but not unregistered because
     # the modified parts concern only POVRAY_RENDER
 
@@ -140,8 +140,8 @@ class TEXTURE_PT_context(TextureButtonsPanel, Panel):
     def poll(cls, context):
         return (
             context.scene.texture_context
-            not in ('MATERIAL', 'WORLD', 'LIGHT', 'PARTICLES', 'LINESTYLE')
-            or context.scene.render.engine != 'POVRAY_RENDER'
+            not in ("MATERIAL", "WORLD", "LIGHT", "PARTICLES", "LINESTYLE")
+            or context.scene.render.engine != "POVRAY_RENDER"
         )
 
     def draw(self, context):
@@ -181,8 +181,8 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
     """Use this class to show pov texture context buttons."""
 
     bl_label = ""
-    bl_options = {'HIDE_HEADER'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"HIDE_HEADER"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -208,7 +208,7 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
         wld = context.scene.world
 
         layout.prop(scene, "texture_context", expand=True)
-        if scene.texture_context == 'MATERIAL' and mat is not None:
+        if scene.texture_context == "MATERIAL" and mat is not None:
 
             row = layout.row()
             row.template_list(
@@ -223,8 +223,8 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
                 type="DEFAULT",
             )
             col = row.column(align=True)
-            col.operator("pov.textureslotadd", icon='ADD', text='')
-            col.operator("pov.textureslotremove", icon='REMOVE', text='')
+            col.operator("pov.textureslotadd", icon="ADD", text="")
+            col.operator("pov.textureslotremove", icon="REMOVE", text="")
             # XXX todo: recreate for pov_texture_slots?
             # col.operator("texture.slot_move", text="", icon='TRIA_UP').type = 'UP'
             # col.operator("texture.slot_move", text="", icon='TRIA_DOWN').type = 'DOWN'
@@ -236,26 +236,29 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
                 try:
                     povtex = slot.texture  # slot.name
                     tex = bpy.data.textures[povtex]
-                    col.prop(tex, 'use_fake_user', text='')
+                    col.prop(tex, "use_fake_user", text="")
                     # layout.label(text='Linked Texture data browser:')
                     # propname = slot.texture_search
                     # if slot.texture was a pointer to texture data rather than just a name string:
                     # layout.template_ID(povtex, "texture", new="texture.new")
                 except KeyError:
                     tex = None
-                layout.prop_search(
-                    slot, 'texture_search', bpy.data, 'textures', text='', icon='TEXTURE'
+                row = layout.row(align=True)
+                row.prop_search(
+                    slot, "texture_search", bpy.data, "textures", text="", icon="TEXTURE"
                 )
-                try:
-                    bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[
-                        slot.texture_search
-                    ]
-                    bpy.context.tool_settings.image_paint.brush.mask_texture = bpy.data.textures[
-                        slot.texture_search
-                    ]
-                except KeyError:
-                    # texture not hand-linked by user
-                    pass
+
+                row.operator("pov.textureslotupdate", icon="FILE_REFRESH", text="")
+                # try:
+                #     bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[
+                #         slot.texture_search
+                #     ]
+                #     bpy.context.tool_settings.image_paint.brush.mask_texture = bpy.data.textures[
+                #         slot.texture_search
+                #     ]
+                # except KeyError:
+                #     # texture not hand-linked by user
+                #     pass
 
                 if tex:
                     layout.separator()
@@ -266,7 +269,7 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
             # else:
             # for i in range(18):  # length of material texture slots
             # mat.pov_texture_slots.add()
-        elif scene.texture_context == 'WORLD' and wld is not None:
+        elif scene.texture_context == "WORLD" and wld is not None:
 
             row = layout.row()
             row.template_list(
@@ -281,8 +284,8 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
                 type="DEFAULT",
             )
             col = row.column(align=True)
-            col.operator("pov.textureslotadd", icon='ADD', text='')
-            col.operator("pov.textureslotremove", icon='REMOVE', text='')
+            col.operator("pov.textureslotadd", icon="ADD", text="")
+            col.operator("pov.textureslotremove", icon="REMOVE", text="")
 
             # todo: recreate for pov_texture_slots?
             # col.operator("texture.slot_move", text="", icon='TRIA_UP').type = 'UP'
@@ -294,14 +297,14 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
                 slot = wld.pov_texture_slots[index]
                 povtex = slot.texture  # slot.name
                 tex = bpy.data.textures[povtex]
-                col.prop(tex, 'use_fake_user', text='')
+                col.prop(tex, "use_fake_user", text="")
                 # layout.label(text='Linked Texture data browser:')
                 # propname = slot.texture_search # NOT USED
                 # if slot.texture was a pointer to texture data rather than just a name string:
                 # layout.template_ID(povtex, "texture", new="texture.new")
 
                 layout.prop_search(
-                    slot, 'texture_search', bpy.data, 'textures', text='', icon='TEXTURE'
+                    slot, "texture_search", bpy.data, "textures", text="", icon="TEXTURE"
                 )
                 try:
                     bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[
@@ -321,9 +324,42 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
                     split.prop(tex, "type", text="")
 
 
+class TEXTURE_OT_POV_context_texture_update(Operator):
+    """Use this class for the texture slot update button."""
+
+    bl_idname = "pov.textureslotupdate"
+    bl_label = "Update"
+    bl_description = "Update texture_slot"
+    bl_options = {"REGISTER", "UNDO"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+
+    @classmethod
+    def poll(cls, context):
+        engine = context.scene.render.engine
+        mate = context.view_layer.objects.active.active_material
+        return mate and engine in cls.COMPAT_ENGINES
+
+    def execute(self, context):
+        # tex.use_fake_user = True
+        mat = context.view_layer.objects.active.active_material
+        index = mat.pov.active_texture_index
+        slot = mat.pov_texture_slots[index]
+        povtex = slot.texture  # slot.name
+        tex = bpy.data.textures[povtex]
+
+        # Switch paint brush and paint brush mask
+        # to this texture so settings remain contextual
+        bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[slot.texture_search]
+        bpy.context.tool_settings.image_paint.brush.mask_texture = bpy.data.textures[
+            slot.texture_search
+        ]
+
+        return {"FINISHED"}
+
+
 # Commented out below is a reminder of what existed in Blender Internal
 # attributes need to be recreated
-'''
+"""
         slot = getattr(context, "texture_slot", None)
         node = getattr(context, "texture_node", None)
         space = context.space_data
@@ -428,15 +464,15 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel):
                     split.prop(slot, "output_node", text="")
             else:
                 split.label(text="Type:")
-'''
+"""
 
 
 class TEXTURE_PT_colors(TextureButtonsPanel, Panel):
     """Use this class to show pov color ramps."""
 
     bl_label = "Colors"
-    bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"DEFAULT_CLOSED"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         layout = self.layout
@@ -475,12 +511,14 @@ class TEXTURE_OT_POV_texture_slot_add(Operator):
     bl_idname = "pov.textureslotadd"
     bl_label = "Add"
     bl_description = "Add texture_slot"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"REGISTER", "UNDO"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         idblock = pov_context_tex_datablock(context)
-        tex = bpy.data.textures.new(name='Texture', type='IMAGE')
+        slot_brush = bpy.data.brushes.new("POVtextureSlot")
+        context.tool_settings.image_paint.brush = slot_brush
+        tex = bpy.data.textures.new(name="Texture", type="IMAGE")
         # tex.use_fake_user = True
         # mat = context.view_layer.objects.active.active_material
         slot = idblock.pov_texture_slots.add()
@@ -497,7 +535,7 @@ class TEXTURE_OT_POV_texture_slot_add(Operator):
         # if area.type in ['PROPERTIES']:
         # area.tag_redraw()
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 class TEXTURE_OT_POV_texture_slot_remove(Operator):
@@ -506,32 +544,35 @@ class TEXTURE_OT_POV_texture_slot_remove(Operator):
     bl_idname = "pov.textureslotremove"
     bl_label = "Remove"
     bl_description = "Remove texture_slot"
-    bl_options = {'REGISTER', 'UNDO'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"REGISTER", "UNDO"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def execute(self, context):
         idblock = pov_context_tex_datablock(context)
         # mat = context.view_layer.objects.active.active_material
         # tex_slot = idblock.pov_texture_slots.remove(idblock.pov.active_texture_index) # not used
+        # tex_to_delete = context.tool_settings.image_paint.brush.texture
+        # bpy.data.textures.remove(tex_to_delete, do_unlink=True, do_id_user=True, do_ui_user=True)
+        idblock.pov_texture_slots.remove(idblock.pov.active_texture_index)
         if idblock.pov.active_texture_index > 0:
             idblock.pov.active_texture_index -= 1
-        try:
-            tex = idblock.pov_texture_slots[idblock.pov.active_texture_index].texture
-        except IndexError:
+            # try:
+            # tex = idblock.pov_texture_slots[idblock.pov.active_texture_index].texture
+            # except IndexError:
             # No more slots
-            return {'FINISHED'}
+            return {"FINISHED"}
         # Switch paint brush to previous texture so settings remain contextual
         # if 'tex' in locals(): # Would test is the tex variable is assigned / exists
-        bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[tex]
-        bpy.context.tool_settings.image_paint.brush.mask_texture = bpy.data.textures[tex]
+        # bpy.context.tool_settings.image_paint.brush.texture = bpy.data.textures[tex]
+        # bpy.context.tool_settings.image_paint.brush.mask_texture = bpy.data.textures[tex]
 
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 class TextureSlotPanel(TextureButtonsPanel):
     """Use this class to show pov texture slots panel."""
 
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     @classmethod
     def poll(cls, context):
@@ -547,8 +588,8 @@ class TEXTURE_PT_POV_type(TextureButtonsPanel, Panel):
     """Use this class to define pov texture type buttons."""
 
     bl_label = "POV Textures"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-    bl_options = {'HIDE_HEADER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+    bl_options = {"HIDE_HEADER"}
 
     def draw(self, context):
         layout = self.layout
@@ -568,8 +609,8 @@ class TEXTURE_PT_POV_preview(TextureButtonsPanel, Panel):
     """Use this class to define pov texture preview panel."""
 
     bl_label = "Preview"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-    bl_options = {'HIDE_HEADER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+    bl_options = {"HIDE_HEADER"}
 
     @classmethod
     def poll(cls, context):
@@ -578,7 +619,7 @@ class TEXTURE_PT_POV_preview(TextureButtonsPanel, Panel):
             return False
         tex = context.texture
         # mat = bpy.context.active_object.active_material #unused
-        return tex and (tex.pov.tex_pattern_type != 'emulator') and (engine in cls.COMPAT_ENGINES)
+        return tex and (tex.pov.tex_pattern_type != "emulator") and (engine in cls.COMPAT_ENGINES)
 
     def draw(self, context):
         tex = context.texture
@@ -587,7 +628,7 @@ class TEXTURE_PT_POV_preview(TextureButtonsPanel, Panel):
         layout = self.layout
         # if idblock:
         # layout.template_preview(tex, parent=idblock, slot=slot)
-        if tex.pov.tex_pattern_type != 'emulator':
+        if tex.pov.tex_pattern_type != "emulator":
             layout.operator("tex.preview_update")
         else:
             layout.template_preview(tex, slot=slot)
@@ -597,28 +638,28 @@ class TEXTURE_PT_POV_parameters(TextureButtonsPanel, Panel):
     """Use this class to define pov texture pattern buttons."""
 
     bl_label = "POV Pattern Options"
-    bl_options = {'HIDE_HEADER'}
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    bl_options = {"HIDE_HEADER"}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw(self, context):
         # mat = bpy.context.active_object.active_material # Unused
         tex = context.texture
-        if tex is not None and tex.pov.tex_pattern_type != 'emulator':
+        if tex is not None and tex.pov.tex_pattern_type != "emulator":
             layout = self.layout
-            if tex.pov.tex_pattern_type == 'agate':
+            if tex.pov.tex_pattern_type == "agate":
                 layout.prop(tex.pov, "modifier_turbulence", text="Agate Turbulence")
-            if tex.pov.tex_pattern_type in {'spiral1', 'spiral2'}:
+            if tex.pov.tex_pattern_type in {"spiral1", "spiral2"}:
                 layout.prop(tex.pov, "modifier_numbers", text="Number of arms")
-            if tex.pov.tex_pattern_type == 'tiling':
+            if tex.pov.tex_pattern_type == "tiling":
                 layout.prop(tex.pov, "modifier_numbers", text="Pattern number")
-            if tex.pov.tex_pattern_type == 'magnet':
+            if tex.pov.tex_pattern_type == "magnet":
                 layout.prop(tex.pov, "magnet_style", text="Magnet style")
             align = True
-            if tex.pov.tex_pattern_type == 'quilted':
+            if tex.pov.tex_pattern_type == "quilted":
                 row = layout.row(align=align)
                 row.prop(tex.pov, "modifier_control0", text="Control0")
                 row.prop(tex.pov, "modifier_control1", text="Control1")
-            if tex.pov.tex_pattern_type == 'brick':
+            if tex.pov.tex_pattern_type == "brick":
                 col = layout.column(align=align)
                 row = col.row()
                 row.prop(tex.pov, "brick_size_x", text="Brick size X")
@@ -626,20 +667,20 @@ class TEXTURE_PT_POV_parameters(TextureButtonsPanel, Panel):
                 row = col.row()
                 row.prop(tex.pov, "brick_size_z", text="Brick size Z")
                 row.prop(tex.pov, "brick_mortar", text="Brick mortar")
-            if tex.pov.tex_pattern_type in {'julia', 'mandel', 'magnet'}:
+            if tex.pov.tex_pattern_type in {"julia", "mandel", "magnet"}:
                 col = layout.column(align=align)
-                if tex.pov.tex_pattern_type == 'julia':
+                if tex.pov.tex_pattern_type == "julia":
                     row = col.row()
                     row.prop(tex.pov, "julia_complex_1", text="Complex 1")
                     row.prop(tex.pov, "julia_complex_2", text="Complex 2")
-                if tex.pov.tex_pattern_type == 'magnet' and tex.pov.magnet_style == 'julia':
+                if tex.pov.tex_pattern_type == "magnet" and tex.pov.magnet_style == "julia":
                     row = col.row()
                     row.prop(tex.pov, "julia_complex_1", text="Complex 1")
                     row.prop(tex.pov, "julia_complex_2", text="Complex 2")
                 row = col.row()
-                if tex.pov.tex_pattern_type in {'julia', 'mandel'}:
+                if tex.pov.tex_pattern_type in {"julia", "mandel"}:
                     row.prop(tex.pov, "f_exponent", text="Exponent")
-                if tex.pov.tex_pattern_type == 'magnet':
+                if tex.pov.tex_pattern_type == "magnet":
                     row.prop(tex.pov, "magnet_type", text="Type")
                 row.prop(tex.pov, "f_iter", text="Iterations")
                 row = col.row()
@@ -648,43 +689,43 @@ class TEXTURE_PT_POV_parameters(TextureButtonsPanel, Panel):
                 row = col.row()
                 row.prop(tex.pov, "f_eor", text="Exterior")
                 row.prop(tex.pov, "f_eor_fac", text="Factor E")
-            if tex.pov.tex_pattern_type == 'gradient':
+            if tex.pov.tex_pattern_type == "gradient":
                 layout.label(text="Gradient orientation:")
                 column_flow = layout.column_flow(columns=3, align=True)
                 column_flow.prop(tex.pov, "grad_orient_x", text="X")
                 column_flow.prop(tex.pov, "grad_orient_y", text="Y")
                 column_flow.prop(tex.pov, "grad_orient_z", text="Z")
-            if tex.pov.tex_pattern_type == 'pavement':
+            if tex.pov.tex_pattern_type == "pavement":
                 layout.prop(tex.pov, "pave_sides", text="Pavement:number of sides")
                 col = layout.column(align=align)
                 column_flow = col.column_flow(columns=3, align=True)
                 column_flow.prop(tex.pov, "pave_tiles", text="Tiles")
-                if tex.pov.pave_sides == '4' and tex.pov.pave_tiles == 6:
+                if tex.pov.pave_sides == "4" and tex.pov.pave_tiles == 6:
                     column_flow.prop(tex.pov, "pave_pat_35", text="Pattern")
-                if tex.pov.pave_sides == '6' and tex.pov.pave_tiles == 5:
+                if tex.pov.pave_sides == "6" and tex.pov.pave_tiles == 5:
                     column_flow.prop(tex.pov, "pave_pat_22", text="Pattern")
-                if tex.pov.pave_sides == '4' and tex.pov.pave_tiles == 5:
+                if tex.pov.pave_sides == "4" and tex.pov.pave_tiles == 5:
                     column_flow.prop(tex.pov, "pave_pat_12", text="Pattern")
-                if tex.pov.pave_sides == '3' and tex.pov.pave_tiles == 6:
+                if tex.pov.pave_sides == "3" and tex.pov.pave_tiles == 6:
                     column_flow.prop(tex.pov, "pave_pat_12", text="Pattern")
-                if tex.pov.pave_sides == '6' and tex.pov.pave_tiles == 4:
+                if tex.pov.pave_sides == "6" and tex.pov.pave_tiles == 4:
                     column_flow.prop(tex.pov, "pave_pat_7", text="Pattern")
-                if tex.pov.pave_sides == '4' and tex.pov.pave_tiles == 4:
+                if tex.pov.pave_sides == "4" and tex.pov.pave_tiles == 4:
                     column_flow.prop(tex.pov, "pave_pat_5", text="Pattern")
-                if tex.pov.pave_sides == '3' and tex.pov.pave_tiles == 5:
+                if tex.pov.pave_sides == "3" and tex.pov.pave_tiles == 5:
                     column_flow.prop(tex.pov, "pave_pat_4", text="Pattern")
-                if tex.pov.pave_sides == '6' and tex.pov.pave_tiles == 3:
+                if tex.pov.pave_sides == "6" and tex.pov.pave_tiles == 3:
                     column_flow.prop(tex.pov, "pave_pat_3", text="Pattern")
-                if tex.pov.pave_sides == '3' and tex.pov.pave_tiles == 4:
+                if tex.pov.pave_sides == "3" and tex.pov.pave_tiles == 4:
                     column_flow.prop(tex.pov, "pave_pat_3", text="Pattern")
-                if tex.pov.pave_sides == '4' and tex.pov.pave_tiles == 3:
+                if tex.pov.pave_sides == "4" and tex.pov.pave_tiles == 3:
                     column_flow.prop(tex.pov, "pave_pat_2", text="Pattern")
-                if tex.pov.pave_sides == '6' and tex.pov.pave_tiles == 6:
+                if tex.pov.pave_sides == "6" and tex.pov.pave_tiles == 6:
                     column_flow.label(text="!!! 5 tiles!")
                 column_flow.prop(tex.pov, "pave_form", text="Form")
-            if tex.pov.tex_pattern_type == 'function':
+            if tex.pov.tex_pattern_type == "function":
                 layout.prop(tex.pov, "func_list", text="Functions")
-            if tex.pov.tex_pattern_type == 'function' and tex.pov.func_list != "NONE":
+            if tex.pov.tex_pattern_type == "function" and tex.pov.func_list != "NONE":
                 func = None
                 if tex.pov.func_list in {"f_noise3d", "f_ph", "f_r", "f_th"}:
                     func = 0
@@ -864,9 +905,9 @@ class TEXTURE_PT_POV_mapping(TextureSlotPanel, Panel):
     """Use this class to define POV texture mapping buttons."""
 
     bl_label = "Mapping"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
 
     @classmethod
     def poll(cls, context):
@@ -894,7 +935,7 @@ class TEXTURE_PT_POV_mapping(TextureSlotPanel, Panel):
             col = split.column()
             col.prop(tex, "texture_coords", text="")
 
-            if tex.texture_coords == 'ORCO':
+            if tex.texture_coords == "ORCO":
                 """
                 ob = context.object
                 if ob and ob.type == 'MESH':
@@ -902,21 +943,21 @@ class TEXTURE_PT_POV_mapping(TextureSlotPanel, Panel):
                     split.label(text="Mesh:")
                     split.prop(ob.data, "texco_mesh", text="")
                 """
-            elif tex.texture_coords == 'UV':
+            elif tex.texture_coords == "UV":
                 split = layout.split(percentage=0.3)
                 split.label(text="Map:")
                 ob = context.object
-                if ob and ob.type == 'MESH':
+                if ob and ob.type == "MESH":
                     split.prop_search(tex, "uv_layer", ob.data, "uv_textures", text="")
                 else:
                     split.prop(tex, "uv_layer", text="")
 
-            elif tex.texture_coords == 'OBJECT':
+            elif tex.texture_coords == "OBJECT":
                 split = layout.split(percentage=0.3)
                 split.label(text="Object:")
                 split.prop(tex, "object", text="")
 
-            elif tex.texture_coords == 'ALONG_STROKE':
+            elif tex.texture_coords == "ALONG_STROKE":
                 split = layout.split(percentage=0.3)
                 split.label(text="Use Tips:")
                 split.prop(tex, "use_tips", text="")
@@ -945,13 +986,13 @@ class TEXTURE_PT_POV_mapping(TextureSlotPanel, Panel):
                 split = layout.split()
 
                 col = split.column()
-                if tex.texture_coords in {'ORCO', 'UV'}:
+                if tex.texture_coords in {"ORCO", "UV"}:
                     col.prop(tex, "use_from_dupli")
-                    if idblock.type == 'VOLUME' and tex.texture_coords == 'ORCO':
+                    if idblock.type == "VOLUME" and tex.texture_coords == "ORCO":
                         col.prop(tex, "use_map_to_bounds")
-                elif tex.texture_coords == 'OBJECT':
+                elif tex.texture_coords == "OBJECT":
                     col.prop(tex, "use_from_original")
-                    if idblock.type == 'VOLUME':
+                    if idblock.type == "VOLUME":
                         col.prop(tex, "use_map_to_bounds")
                 else:
                     col.label()
@@ -971,9 +1012,9 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel):
     """Use this class to define pov texture influence buttons."""
 
     bl_label = "Influence"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
     # bl_context = 'texture'
 
     @classmethod
@@ -982,7 +1023,7 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel):
         if (
             # isinstance(idblock, Brush) and # Brush used for everything since 2.8
             context.scene.texture_context
-            == 'OTHER'
+            == "OTHER"
         ):  # XXX replace by isinstance(idblock, bpy.types.Brush) and ...
             return False
 
@@ -1006,7 +1047,9 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel):
         ]  # bpy.data.textures[mat.active_texture_index]
         # below tex unused yet ...maybe for particles?
         try:
-            tex = bpy.data.textures[idblock.pov_texture_slots[idblock.pov.active_texture_index].texture]  # NOT USED
+            tex = bpy.data.textures[
+                idblock.pov_texture_slots[idblock.pov.active_texture_index].texture
+            ]  # NOT USED
         except KeyError:
             tex = None  # NOT USED
 
@@ -1022,7 +1065,7 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel):
             split = layout.split()
 
             col = split.column()
-            if idblock.pov.type in {'SURFACE', 'WIRE'}:
+            if idblock.pov.type in {"SURFACE", "WIRE"}:
 
                 split = layout.split()
 
@@ -1054,7 +1097,7 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel):
                 factor_but(col, "use_map_warp", "warp_factor", "Warp")
                 factor_but(col, "use_map_displacement", "displacement_factor", "Displace")
 
-            elif idblock.pov.type == 'HALO':
+            elif idblock.pov.type == "HALO":
                 layout.label(text="Halo:")
 
                 split = layout.split()
@@ -1067,7 +1110,7 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel):
                 factor_but(col, "use_map_raymir", "raymir_factor", "Size")
                 factor_but(col, "use_map_hardness", "hardness_factor", "Hardness")
                 factor_but(col, "use_map_translucency", "translucency_factor", "Add")
-            elif idblock.pov.type == 'VOLUME':
+            elif idblock.pov.type == "VOLUME":
                 layout.label(text="Volume:")
 
                 split = layout.split()
@@ -1182,12 +1225,12 @@ class TEXTURE_PT_POV_tex_gamma(TextureButtonsPanel, Panel):
     """Use this class to define pov texture gamma buttons."""
 
     bl_label = "Image Gamma"
-    COMPAT_ENGINES = {'POVRAY_RENDER'}
+    COMPAT_ENGINES = {"POVRAY_RENDER"}
 
     def draw_header(self, context):
         tex = context.texture
 
-        self.layout.prop(tex.pov, "tex_gamma_enable", text="", icon='SEQ_LUMA_WAVEFORM')
+        self.layout.prop(tex.pov, "tex_gamma_enable", text="", icon="SEQ_LUMA_WAVEFORM")
 
     def draw(self, context):
         layout = self.layout
@@ -1226,6 +1269,7 @@ classes = (
     MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist,
     TEXTURE_OT_POV_texture_slot_add,
     TEXTURE_OT_POV_texture_slot_remove,
+    TEXTURE_OT_POV_context_texture_update,
     TEXTURE_PT_POV_influence,
     TEXTURE_PT_POV_mapping,
 )
diff --git a/render_povray/texturing_procedural.py b/render_povray/texturing_procedural.py
new file mode 100644
index 000000000..df707630f
--- /dev/null
+++ b/render_povray/texturing_procedural.py
@@ -0,0 +1,694 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""Use Blender procedural textures exported to POV patterns."""
+
+import bpy
+
+
+def export_pattern(texture):
+    """Translate Blender procedural textures to POV patterns and write to pov file.
+
+    Function Patterns can be used to better access sub components of a pattern like
+    grey values for influence mapping
+    """
+    from .render import string_strip_hyphen
+
+    tex = texture
+    pat = tex.pov
+    pat_name = "PAT_%s" % string_strip_hyphen(bpy.path.clean_name(tex.name))
+    mapping_dif = "translate <%.4g,%.4g,%.4g> scale <%.4g,%.4g,%.4g>" % (
+        pat.tex_mov_x,
+        pat.tex_mov_y,
+        pat.tex_mov_z,
+        1.0 / pat.tex_scale_x,
+        1.0 / pat.tex_scale_y,
+        1.0 / pat.tex_scale_z,
+    )
+    text_strg = ""
+
+    def export_color_ramp(texture):
+        tex = texture
+        pat = tex.pov
+        col_ramp_strg = "color_map {\n"
+        for num_color, el in enumerate(tex.color_ramp.elements, start=1):
+            pos = el.position
+            col = el.color
+            col_r, col_g, col_b, col_a = col[0], col[1], col[2], 1 - col[3]
+            if pat.tex_pattern_type not in {
+                "checker",
+                "hexagon",
+                "square",
+                "triangular",
+                "brick",
+            }:
+                col_ramp_strg += "[%.4g color rgbf<%.4g,%.4g,%.4g,%.4g>] \n" % (
+                    pos,
+                    col_r,
+                    col_g,
+                    col_b,
+                    col_a,
+                )
+            if pat.tex_pattern_type in {"brick", "checker"} and num_color < 3:
+                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                    col_r,
+                    col_g,
+                    col_b,
+                    col_a,
+                )
+            if pat.tex_pattern_type == "hexagon" and num_color < 4:
+                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                    col_r,
+                    col_g,
+                    col_b,
+                    col_a,
+                )
+            if pat.tex_pattern_type == "square" and num_color < 5:
+                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                    col_r,
+                    col_g,
+                    col_b,
+                    col_a,
+                )
+            if pat.tex_pattern_type == "triangular" and num_color < 7:
+                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                    col_r,
+                    col_g,
+                    col_b,
+                    col_a,
+                )
+
+        col_ramp_strg += "} \n"
+        # end color map
+        return col_ramp_strg
+
+    # much work to be done here only defaults translated for now:
+    # pov noise_generator 3 means perlin noise
+    if tex.type not in {"NONE", "IMAGE"} and pat.tex_pattern_type == "emulator":
+        text_strg += "pigment {\n"
+        # ------------------------- EMULATE BLENDER VORONOI TEXTURE ------------------------- #
+        if tex.type == "VORONOI":
+            text_strg += "crackle\n"
+            text_strg += "    offset %.4g\n" % tex.nabla
+            text_strg += "    form <%.4g,%.4g,%.4g>\n" % (
+                tex.weight_1,
+                tex.weight_2,
+                tex.weight_3,
+            )
+            if tex.distance_metric == "DISTANCE":
+                text_strg += "    metric 2.5\n"
+            if tex.distance_metric == "DISTANCE_SQUARED":
+                text_strg += "    metric 2.5\n"
+                text_strg += "    poly_wave 2\n"
+            if tex.distance_metric == "MINKOVSKY":
+                text_strg += "    metric %s\n" % tex.minkovsky_exponent
+            if tex.distance_metric == "MINKOVSKY_FOUR":
+                text_strg += "    metric 4\n"
+            if tex.distance_metric == "MINKOVSKY_HALF":
+                text_strg += "    metric 0.5\n"
+            if tex.distance_metric == "CHEBYCHEV":
+                text_strg += "    metric 10\n"
+            if tex.distance_metric == "MANHATTAN":
+                text_strg += "    metric 1\n"
+
+            if tex.color_mode == "POSITION":
+                text_strg += "solid\n"
+            text_strg += "scale 0.25\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0 color rgbt<0,0,0,1>]\n"
+                text_strg += "[1 color rgbt<1,1,1,0>]\n"
+                text_strg += "}\n"
+
+        # ------------------------- EMULATE BLENDER CLOUDS TEXTURE ------------------------- #
+        if tex.type == "CLOUDS":
+            if tex.noise_type == "SOFT_NOISE":
+                text_strg += "wrinkles\n"
+                text_strg += "scale 0.25\n"
+            else:
+                text_strg += "granite\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0 color rgbt<0,0,0,1>]\n"
+                text_strg += "[1 color rgbt<1,1,1,0>]\n"
+                text_strg += "}\n"
+
+        # ------------------------- EMULATE BLENDER WOOD TEXTURE ------------------------- #
+        if tex.type == "WOOD":
+            if tex.wood_type == "RINGS":
+                text_strg += "wood\n"
+                text_strg += "scale 0.25\n"
+            if tex.wood_type == "RINGNOISE":
+                text_strg += "wood\n"
+                text_strg += "scale 0.25\n"
+                text_strg += "turbulence %.4g\n" % (tex.turbulence / 100)
+            if tex.wood_type == "BANDS":
+                text_strg += "marble\n"
+                text_strg += "scale 0.25\n"
+                text_strg += "rotate <45,-45,45>\n"
+            if tex.wood_type == "BANDNOISE":
+                text_strg += "marble\n"
+                text_strg += "scale 0.25\n"
+                text_strg += "rotate <45,-45,45>\n"
+                text_strg += "turbulence %.4g\n" % (tex.turbulence / 10)
+
+            if tex.noise_basis_2 == "SIN":
+                text_strg += "sine_wave\n"
+            if tex.noise_basis_2 == "TRI":
+                text_strg += "triangle_wave\n"
+            if tex.noise_basis_2 == "SAW":
+                text_strg += "ramp_wave\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0 color rgbt<0,0,0,0>]\n"
+                text_strg += "[1 color rgbt<1,1,1,0>]\n"
+                text_strg += "}\n"
+
+        # ------------------------- EMULATE BLENDER STUCCI TEXTURE ------------------------- #
+        if tex.type == "STUCCI":
+            text_strg += "bozo\n"
+            text_strg += "scale 0.25\n"
+            if tex.noise_type == "HARD_NOISE":
+                text_strg += "triangle_wave\n"
+                if tex.use_color_ramp:
+                    text_strg += export_color_ramp(tex)
+                else:
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbf<1,1,1,0>]\n"
+                    text_strg += "[1 color rgbt<0,0,0,1>]\n"
+                    text_strg += "}\n"
+            else:
+                if tex.use_color_ramp:
+                    text_strg += export_color_ramp(tex)
+                else:
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbf<0,0,0,1>]\n"
+                    text_strg += "[1 color rgbt<1,1,1,0>]\n"
+                    text_strg += "}\n"
+
+        # ------------------------- EMULATE BLENDER MAGIC TEXTURE ------------------------- #
+        if tex.type == "MAGIC":
+            text_strg += "leopard\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0 color rgbt<1,1,1,0.5>]\n"
+                text_strg += "[0.25 color rgbf<0,1,0,0.75>]\n"
+                text_strg += "[0.5 color rgbf<0,0,1,0.75>]\n"
+                text_strg += "[0.75 color rgbf<1,0,1,0.75>]\n"
+                text_strg += "[1 color rgbf<0,1,0,0.75>]\n"
+                text_strg += "}\n"
+            text_strg += "scale 0.1\n"
+
+        # ------------------------- EMULATE BLENDER MARBLE TEXTURE ------------------------- #
+        if tex.type == "MARBLE":
+            text_strg += "marble\n"
+            text_strg += "turbulence 0.5\n"
+            text_strg += "noise_generator 3\n"
+            text_strg += "scale 0.75\n"
+            text_strg += "rotate <45,-45,45>\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                if tex.marble_type == "SOFT":
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbt<0,0,0,0>]\n"
+                    text_strg += "[0.05 color rgbt<0,0,0,0>]\n"
+                    text_strg += "[1 color rgbt<0.9,0.9,0.9,0>]\n"
+                    text_strg += "}\n"
+                elif tex.marble_type == "SHARP":
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbt<0,0,0,0>]\n"
+                    text_strg += "[0.025 color rgbt<0,0,0,0>]\n"
+                    text_strg += "[1 color rgbt<0.9,0.9,0.9,0>]\n"
+                    text_strg += "}\n"
+                else:
+                    text_strg += "[0 color rgbt<0,0,0,0>]\n"
+                    text_strg += "[1 color rgbt<1,1,1,0>]\n"
+                    text_strg += "}\n"
+            if tex.noise_basis_2 == "SIN":
+                text_strg += "sine_wave\n"
+            if tex.noise_basis_2 == "TRI":
+                text_strg += "triangle_wave\n"
+            if tex.noise_basis_2 == "SAW":
+                text_strg += "ramp_wave\n"
+
+        # ------------------------- EMULATE BLENDER BLEND TEXTURE ------------------------- #
+        if tex.type == "BLEND":
+            if tex.progression == "RADIAL":
+                text_strg += "radial\n"
+                if tex.use_flip_axis == "HORIZONTAL":
+                    text_strg += "rotate x*90\n"
+                else:
+                    text_strg += "rotate <-90,0,90>\n"
+                text_strg += "ramp_wave\n"
+            elif tex.progression == "SPHERICAL":
+                text_strg += "spherical\n"
+                text_strg += "scale 3\n"
+                text_strg += "poly_wave 1\n"
+            elif tex.progression == "QUADRATIC_SPHERE":
+                text_strg += "spherical\n"
+                text_strg += "scale 3\n"
+                text_strg += "    poly_wave 2\n"
+            elif tex.progression == "DIAGONAL":
+                text_strg += "gradient <1,1,0>\n"
+                text_strg += "scale 3\n"
+            elif tex.use_flip_axis == "HORIZONTAL":
+                text_strg += "gradient x\n"
+                text_strg += "scale 2.01\n"
+            elif tex.use_flip_axis == "VERTICAL":
+                text_strg += "gradient y\n"
+                text_strg += "scale 2.01\n"
+            # text_strg+="ramp_wave\n"
+            # text_strg+="frequency 0.5\n"
+            text_strg += "phase 0.5\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0 color rgbt<1,1,1,0>]\n"
+                text_strg += "[1 color rgbf<0,0,0,1>]\n"
+                text_strg += "}\n"
+            if tex.progression == "LINEAR":
+                text_strg += "    poly_wave 1\n"
+            if tex.progression == "QUADRATIC":
+                text_strg += "    poly_wave 2\n"
+            if tex.progression == "EASING":
+                text_strg += "    poly_wave 1.5\n"
+
+        # ------------------------- EMULATE BLENDER MUSGRAVE TEXTURE ------------------------- #
+        # if tex.type == 'MUSGRAVE':
+        # text_strg+="function{ f_ridged_mf( x, y, 0, 1, 2, 9, -0.5, 3,3 )*0.5}\n"
+        # text_strg+="color_map {\n"
+        # text_strg+="[0 color rgbf<0,0,0,1>]\n"
+        # text_strg+="[1 color rgbf<1,1,1,0>]\n"
+        # text_strg+="}\n"
+        # simplified for now:
+
+        if tex.type == "MUSGRAVE":
+            text_strg += "bozo scale 0.25 \n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += (
+                    "color_map {[0.5 color rgbf<0,0,0,1>][1 color rgbt<1,1,1,0>]}ramp_wave \n"
+                )
+
+        # ------------------------- EMULATE BLENDER DISTORTED NOISE TEXTURE ------------------------- #
+        if tex.type == "DISTORTED_NOISE":
+            text_strg += "average\n"
+            text_strg += "  pigment_map {\n"
+            text_strg += "  [1 bozo scale 0.25 turbulence %.4g\n" % tex.distortion
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0 color rgbt<1,1,1,0>]\n"
+                text_strg += "[1 color rgbf<0,0,0,1>]\n"
+                text_strg += "}\n"
+            text_strg += "]\n"
+
+            if tex.noise_distortion == "CELL_NOISE":
+                text_strg += "  [1 cells scale 0.1\n"
+                if tex.use_color_ramp:
+                    text_strg += export_color_ramp(tex)
+                else:
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
+                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
+                    text_strg += "}\n"
+                text_strg += "]\n"
+            if tex.noise_distortion == "VORONOI_CRACKLE":
+                text_strg += "  [1 crackle scale 0.25\n"
+                if tex.use_color_ramp:
+                    text_strg += export_color_ramp(tex)
+                else:
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
+                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
+                    text_strg += "}\n"
+                text_strg += "]\n"
+            if tex.noise_distortion in [
+                "VORONOI_F1",
+                "VORONOI_F2",
+                "VORONOI_F3",
+                "VORONOI_F4",
+                "VORONOI_F2_F1",
+            ]:
+                text_strg += "  [1 crackle metric 2.5 scale 0.25 turbulence %.4g\n" % (
+                    tex.distortion / 2
+                )
+                if tex.use_color_ramp:
+                    text_strg += export_color_ramp(tex)
+                else:
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
+                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
+                    text_strg += "}\n"
+                text_strg += "]\n"
+            else:
+                text_strg += "  [1 wrinkles scale 0.25\n"
+                if tex.use_color_ramp:
+                    text_strg += export_color_ramp(tex)
+                else:
+                    text_strg += "color_map {\n"
+                    text_strg += "[0 color rgbt<1,1,1,0>]\n"
+                    text_strg += "[1 color rgbf<0,0,0,1>]\n"
+                    text_strg += "}\n"
+                text_strg += "]\n"
+            text_strg += "  }\n"
+
+        # ------------------------- EMULATE BLENDER NOISE TEXTURE ------------------------- #
+        if tex.type == "NOISE":
+            text_strg += "cells\n"
+            text_strg += "turbulence 3\n"
+            text_strg += "omega 3\n"
+            if tex.use_color_ramp:
+                text_strg += export_color_ramp(tex)
+            else:
+                text_strg += "color_map {\n"
+                text_strg += "[0.75 color rgb<0,0,0,>]\n"
+                text_strg += "[1 color rgb<1,1,1,>]\n"
+                text_strg += "}\n"
+
+        # ------------------------- IGNORE OTHER BLENDER TEXTURE ------------------------- #
+        else:  # non translated textures
+            pass
+        text_strg += "}\n\n"
+
+        text_strg += "#declare f%s=\n" % pat_name
+        text_strg += "function{pigment{%s}}\n" % pat_name
+        text_strg += "\n"
+
+    elif pat.tex_pattern_type != "emulator":
+        text_strg += "pigment {\n"
+        text_strg += "%s\n" % pat.tex_pattern_type
+        if pat.tex_pattern_type == "agate":
+            text_strg += "agate_turb %.4g\n" % pat.modifier_turbulence
+        if pat.tex_pattern_type in {"spiral1", "spiral2", "tiling"}:
+            text_strg += "%s\n" % pat.modifier_numbers
+        if pat.tex_pattern_type == "quilted":
+            text_strg += "control0 %s control1 %s\n" % (
+                pat.modifier_control0,
+                pat.modifier_control1,
+            )
+        if pat.tex_pattern_type == "mandel":
+            text_strg += "%s exponent %s \n" % (pat.f_iter, pat.f_exponent)
+        if pat.tex_pattern_type == "julia":
+            text_strg += "<%.4g, %.4g> %s exponent %s \n" % (
+                pat.julia_complex_1,
+                pat.julia_complex_2,
+                pat.f_iter,
+                pat.f_exponent,
+            )
+        if pat.tex_pattern_type == "magnet" and pat.magnet_style == "mandel":
+            text_strg += "%s mandel %s \n" % (pat.magnet_type, pat.f_iter)
+        if pat.tex_pattern_type == "magnet" and pat.magnet_style == "julia":
+            text_strg += "%s julia <%.4g, %.4g> %s\n" % (
+                pat.magnet_type,
+                pat.julia_complex_1,
+                pat.julia_complex_2,
+                pat.f_iter,
+            )
+        if pat.tex_pattern_type in {"mandel", "julia", "magnet"}:
+            text_strg += "interior %s, %.4g\n" % (pat.f_ior, pat.f_ior_fac)
+            text_strg += "exterior %s, %.4g\n" % (pat.f_eor, pat.f_eor_fac)
+        if pat.tex_pattern_type == "gradient":
+            text_strg += "<%s, %s, %s> \n" % (
+                pat.grad_orient_x,
+                pat.grad_orient_y,
+                pat.grad_orient_z,
+            )
+        if pat.tex_pattern_type == "pavement":
+            num_tiles = pat.pave_tiles
+            num_pattern = 1
+            if pat.pave_sides == "4" and pat.pave_tiles == 3:
+                num_pattern = pat.pave_pat_2
+            if pat.pave_sides == "6" and pat.pave_tiles == 3:
+                num_pattern = pat.pave_pat_3
+            if pat.pave_sides == "3" and pat.pave_tiles == 4:
+                num_pattern = pat.pave_pat_3
+            if pat.pave_sides == "3" and pat.pave_tiles == 5:
+                num_pattern = pat.pave_pat_4
+            if pat.pave_sides == "4" and pat.pave_tiles == 4:
+                num_pattern = pat.pave_pat_5
+            if pat.pave_sides == "6" and pat.pave_tiles == 4:
+                num_pattern = pat.pave_pat_7
+            if pat.pave_sides == "4" and pat.pave_tiles == 5:
+                num_pattern = pat.pave_pat_12
+            if pat.pave_sides == "3" and pat.pave_tiles == 6:
+                num_pattern = pat.pave_pat_12
+            if pat.pave_sides == "6" and pat.pave_tiles == 5:
+                num_pattern = pat.pave_pat_22
+            if pat.pave_sides == "4" and pat.pave_tiles == 6:
+                num_pattern = pat.pave_pat_35
+            if pat.pave_sides == "6" and pat.pave_tiles == 6:
+                num_tiles = 5
+            text_strg += "number_of_sides %s number_of_tiles %s pattern %s form %s \n" % (
+                pat.pave_sides,
+                num_tiles,
+                num_pattern,
+                pat.pave_form,
+            )
+        # ------------------------- functions ------------------------- #
+        if pat.tex_pattern_type == "function":
+            text_strg += "{ %s" % pat.func_list
+            text_strg += "(x"
+            if pat.func_plus_x != "NONE":
+                if pat.func_plus_x == "increase":
+                    text_strg += "*"
+                if pat.func_plus_x == "plus":
+                    text_strg += "+"
+                text_strg += "%.4g" % pat.func_x
+            text_strg += ",y"
+            if pat.func_plus_y != "NONE":
+                if pat.func_plus_y == "increase":
+                    text_strg += "*"
+                if pat.func_plus_y == "plus":
+                    text_strg += "+"
+                text_strg += "%.4g" % pat.func_y
+            text_strg += ",z"
+            if pat.func_plus_z != "NONE":
+                if pat.func_plus_z == "increase":
+                    text_strg += "*"
+                if pat.func_plus_z == "plus":
+                    text_strg += "+"
+                text_strg += "%.4g" % pat.func_z
+            sort = -1
+            if pat.func_list in {
+                "f_comma",
+                "f_crossed_trough",
+                "f_cubic_saddle",
+                "f_cushion",
+                "f_devils_curve",
+                "f_enneper",
+                "f_glob",
+                "f_heart",
+                "f_hex_x",
+                "f_hex_y",
+                "f_hunt_surface",
+                "f_klein_bottle",
+                "f_kummer_surface_v1",
+                "f_lemniscate_of_gerono",
+                "f_mitre",
+                "f_nodal_cubic",
+                "f_noise_generator",
+                "f_odd",
+                "f_paraboloid",
+                "f_pillow",
+                "f_piriform",
+                "f_quantum",
+                "f_quartic_paraboloid",
+                "f_quartic_saddle",
+                "f_sphere",
+                "f_steiners_roman",
+                "f_torus_gumdrop",
+                "f_umbrella",
+            }:
+                sort = 0
+            if pat.func_list in {
+                "f_bicorn",
+                "f_bifolia",
+                "f_boy_surface",
+                "f_superellipsoid",
+                "f_torus",
+            }:
+                sort = 1
+            if pat.func_list in {
+                "f_ellipsoid",
+                "f_folium_surface",
+                "f_hyperbolic_torus",
+                "f_kampyle_of_eudoxus",
+                "f_parabolic_torus",
+                "f_quartic_cylinder",
+                "f_torus2",
+            }:
+                sort = 2
+            if pat.func_list in {
+                "f_blob2",
+                "f_cross_ellipsoids",
+                "f_flange_cover",
+                "f_isect_ellipsoids",
+                "f_kummer_surface_v2",
+                "f_ovals_of_cassini",
+                "f_rounded_box",
+                "f_spikes_2d",
+                "f_strophoid",
+            }:
+                sort = 3
+            if pat.func_list in {
+                "f_algbr_cyl1",
+                "f_algbr_cyl2",
+                "f_algbr_cyl3",
+                "f_algbr_cyl4",
+                "f_blob",
+                "f_mesh1",
+                "f_poly4",
+                "f_spikes",
+            }:
+                sort = 4
+            if pat.func_list in {
+                "f_devils_curve_2d",
+                "f_dupin_cyclid",
+                "f_folium_surface_2d",
+                "f_hetero_mf",
+                "f_kampyle_of_eudoxus_2d",
+                "f_lemniscate_of_gerono_2d",
+                "f_polytubes",
+                "f_ridge",
+                "f_ridged_mf",
+                "f_spiral",
+                "f_witch_of_agnesi",
+            }:
+                sort = 5
+            if pat.func_list in {
+                "f_helix1",
+                "f_helix2",
+                "f_piriform_2d",
+                "f_strophoid_2d",
+            }:
+                sort = 6
+            if pat.func_list == "f_helical_torus":
+                sort = 7
+            if sort > -1:
+                text_strg += ",%.4g" % pat.func_P0
+            if sort > 0:
+                text_strg += ",%.4g" % pat.func_P1
+            if sort > 1:
+                text_strg += ",%.4g" % pat.func_P2
+            if sort > 2:
+                text_strg += ",%.4g" % pat.func_P3
+            if sort > 3:
+                text_strg += ",%.4g" % pat.func_P4
+            if sort > 4:
+                text_strg += ",%.4g" % pat.func_P5
+            if sort > 5:
+                text_strg += ",%.4g" % pat.func_P6
+            if sort > 6:
+                text_strg += ",%.4g" % pat.func_P7
+                text_strg += ",%.4g" % pat.func_P8
+                text_strg += ",%.4g" % pat.func_P9
+            text_strg += ")}\n"
+        # ------------------------- end functions ------------------------- #
+        if pat.tex_pattern_type not in {
+            "checker",
+            "hexagon",
+            "square",
+            "triangular",
+            "brick",
+        }:
+            text_strg += "color_map {\n"
+        if tex.use_color_ramp:
+            for num_color, el in enumerate(tex.color_ramp.elements, start=1):
+                pos = el.position
+                col = el.color
+                col_r, col_g, col_b, col_a = col[0], col[1], col[2], 1 - col[3]
+                if pat.tex_pattern_type not in {
+                    "checker",
+                    "hexagon",
+                    "square",
+                    "triangular",
+                    "brick",
+                }:
+                    text_strg += "[%.4g color rgbf<%.4g,%.4g,%.4g,%.4g>] \n" % (
+                        pos,
+                        col_r,
+                        col_g,
+                        col_b,
+                        col_a,
+                    )
+                if pat.tex_pattern_type in {"brick", "checker"} and num_color < 3:
+                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                        col_r,
+                        col_g,
+                        col_b,
+                        col_a,
+                    )
+                if pat.tex_pattern_type == "hexagon" and num_color < 4:
+                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                        col_r,
+                        col_g,
+                        col_b,
+                        col_a,
+                    )
+                if pat.tex_pattern_type == "square" and num_color < 5:
+                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                        col_r,
+                        col_g,
+                        col_b,
+                        col_a,
+                    )
+                if pat.tex_pattern_type == "triangular" and num_color < 7:
+                    text_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (
+                        col_r,
+                        col_g,
+                        col_b,
+                        col_a,
+                    )
+        else:
+            text_strg += "[0 color rgbf<0,0,0,1>]\n"
+            text_strg += "[1 color rgbf<1,1,1,0>]\n"
+        if pat.tex_pattern_type not in {
+            "checker",
+            "hexagon",
+            "square",
+            "triangular",
+            "brick",
+        }:
+            text_strg += "} \n"
+        if pat.tex_pattern_type == "brick":
+            text_strg += "brick_size <%.4g, %.4g, %.4g> mortar %.4g \n" % (
+                pat.brick_size_x,
+                pat.brick_size_y,
+                pat.brick_size_z,
+                pat.brick_mortar,
+            )
+        text_strg += "%s \n" % mapping_dif
+        text_strg += "rotate <%.4g,%.4g,%.4g> \n" % (
+            pat.tex_rot_x,
+            pat.tex_rot_y,
+            pat.tex_rot_z,
+        )
+        text_strg += "turbulence <%.4g,%.4g,%.4g> \n" % (
+            pat.warp_turbulence_x,
+            pat.warp_turbulence_y,
+            pat.warp_turbulence_z,
+        )
+        text_strg += "octaves %s \n" % pat.modifier_octaves
+        text_strg += "lambda %.4g \n" % pat.modifier_lambda
+        text_strg += "omega %.4g \n" % pat.modifier_omega
+        text_strg += "frequency %.4g \n" % pat.modifier_frequency
+        text_strg += "phase %.4g \n" % pat.modifier_phase
+        text_strg += "}\n\n"
+        text_strg += "#declare f%s=\n" % pat_name
+        text_strg += "function{pigment{%s}}\n" % pat_name
+        text_strg += "\n"
+    return text_strg
diff --git a/render_povray/texturing_properties.py b/render_povray/texturing_properties.py
index fbfb81259..8dc2038df 100755
--- a/render_povray/texturing_properties.py
+++ b/render_povray/texturing_properties.py
@@ -29,7 +29,7 @@ class MaterialTextureSlot(PropertyGroup):
     """Declare material texture slot level properties for UI and translated to POV."""
 
     bl_idname = ("pov_texture_slots",)
-    bl_description = ("Texture_slots from Blender-2.79",)
+    bl_description = ("Nodeless texture slots",)
 
     # Adding a "real" texture datablock as property is not possible
     # (or at least not easy through a dynamically populated EnumProperty).
@@ -367,86 +367,17 @@ class MaterialTextureSlot(PropertyGroup):
         description="Amount texture affects texture coordinates of next channels",
         default=0.0,
     )
-
     # ---------------------------------------------------------------- #
-
-    blend_factor: FloatProperty(
-        name="Blend",
-        description="Amount texture affects color progression of the " "background",
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-    )
-
-    horizon_factor: FloatProperty(
-        name="Horizon",
-        description="Amount texture affects color of the horizon" "",
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-    )
-
-    object: StringProperty(
-        name="Object",
-        description="Object to use for mapping with Object texture coordinates",
-        default="",
-    )
-
-    texture_coords: EnumProperty(
-        name="Coordinates",
-        description="Texture coordinates used to map the texture onto the background",
-        items=(
-            ("VIEW", "View", "Use view vector for the texture coordinates"),
-            (
-                "GLOBAL",
-                "Global",
-                "Use global coordinates for the texture coordinates (interior mist)",
-            ),
-            (
-                "ANGMAP",
-                "AngMap",
-                "Use 360 degree angular coordinates, e.g. for spherical light probes",
-            ),
-            ("SPHERE", "Sphere", "For 360 degree panorama sky, spherical mapped, only top half"),
-            ("EQUIRECT", "Equirectangular", "For 360 degree panorama sky, equirectangular mapping"),
-            ("TUBE", "Tube", "For 360 degree panorama sky, cylindrical mapped, only top half"),
-            ("OBJECT", "Object", "Use linked object’s coordinates for texture coordinates"),
-        ),
-        default="VIEW",
-    )
-
-    use_map_blend: BoolProperty(
-        name="Blend Map", description="Affect the color progression of the background", default=True
-    )
-
-    use_map_horizon: BoolProperty(
-        name="Horizon Map", description="Affect the color of the horizon", default=False
-    )
-
-    use_map_zenith_down: BoolProperty(
-        name="", description="Affect the color of the zenith below", default=False
-    )
-
-    use_map_zenith_up: BoolProperty(
-        name="Zenith Up Map", description="Affect the color of the zenith above", default=False
-    )
-
-    zenith_down_factor: FloatProperty(
-        name="Zenith Down",
-        description="Amount texture affects color of the zenith below",
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-    )
-
-    zenith_up_factor: FloatProperty(
-        name="Zenith Up",
-        description="Amount texture affects color of the zenith above",
-        soft_min=0.0,
-        soft_max=1.0,
-        default=1.0,
-    )
-
+    # so that its for loop stays one level less nested
+    # used_texture_slots generator expression requires :
+    def __iter__(self):
+        return self
+    def __next__(self):
+        tex = bpy.data.textures[self.texture]
+        while tex.pov.active_texture_index < len(bpy.data.textures): # XXX should use slots count
+            tex.pov.active_texture_index += 1
+        raise StopIteration
+    # ---------------------------------------------------------------- #
 
 # ---------------------------------------------------------------- #
 # Texture slots (World context) exported as POV texture properties.
diff --git a/render_povray/ui_core.py b/render_povray/ui_core.py
new file mode 100644
index 000000000..2768bea94
--- /dev/null
+++ b/render_povray/ui_core.py
@@ -0,0 +1,280 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# <pep8 compliant>
+
+"""User interface imports and preferences for the addon."""
+
+# import addon_utils
+# from time import sleep
+import bpy
+import os
+
+from bpy.app.handlers import persistent
+from pathlib import Path
+
+# from bpy.utils import register_class, unregister_class
+# from bpy.types import (
+# Operator,
+# Menu,
+# UIList,
+# Panel,
+# Brush,
+# Material,
+# Light,
+# World,
+# ParticleSettings,
+# FreestyleLineStyle,
+# )
+
+# from bl_operators.presets import AddPresetBase
+
+from . import (
+    render_gui,
+    scenography_gui,
+    model_gui,
+    shading_gui,
+    texturing_gui,
+    nodes,  # for POV specific nodes
+    scripting_gui,
+    update_files,
+)
+
+
+# ------------ POV-Centric WORKSPACE ------------ #
+@persistent
+def pov_centric_moray_like_workspace(dummy):
+    """Set up a POV centric Workspace if addon was activated and saved as default renderer.
+
+    This would bring a ’_RestrictData’ error because UI needs to be fully loaded before
+    workspace changes so registering this function in bpy.app.handlers is needed.
+    By default handlers are freed when loading new files, but here we want the handler
+    to stay running across multiple files as part of this add-on. That is why the
+    bpy.app.handlers.persistent decorator is used (@persistent) above.
+    """
+    # Scripting workspace may have been altered from factory though, so should
+    # we put all within a Try... Except AttributeErrors ? Any better solution ?
+    # Should it simply not run when opening existing file? be a preferences operator to create
+    # Moray like workspace
+
+
+    # -----------------------------------UTF-8---------------------------------- #
+    # Check and fix all strings in current .blend file to be valid UTF-8 Unicode
+    # sometimes needed for old, 2.4x / 2.6x area files
+    try:
+        bpy.ops.wm.blend_strings_utf8_validate()
+    except BaseException as e:
+        print(e.__doc__)
+        print("An exception occurred: {}".format(e))
+        pass
+    # --------------------------------Workspaces------------------------------- #
+
+    # If this file is not the default do nothing so as to not mess up project dependant workspaces
+    if bpy.data.filepath:
+        return
+
+    available_workspaces = bpy.data.workspaces
+
+    if all(tabs in available_workspaces for tabs in ["POV-Mo", "POV-Ed"]):
+        print(
+            "\nPOV-Mo and POV-Ed tabs respectively provide GUI and TEXT\n"
+            "oriented POV workspaces akin to Moray and POVWIN"
+        )
+        return
+    if "POV-Ed" not in available_workspaces:
+        print(
+            "\nTo use POV centric workspaces you can set POV render option\n"
+            "and save it with File > Defaults > Save Startup File menu"
+        )
+        try:
+            if all(
+                othertabs not in available_workspaces
+                for othertabs in ["Geometry Nodes", "POV-Ed"]
+            ):
+                bpy.ops.workspace.append_activate(
+                    idname="Geometry Nodes",
+                    filepath=os.path.join(bpy.utils.user_resource("CONFIG"), "startup.blend"),
+                )
+        except BaseException as e:
+            print(e.__doc__)
+            print("An exception occurred: {}".format(e))
+            try:
+                # Last resort: try to import from the blender templates
+                for p in Path(next(bpy.utils.app_template_paths())).rglob("startup.blend"):
+                    bpy.ops.workspace.append_activate(idname="Geometry Nodes", filepath=str(p))
+            except BaseException as e:
+                print(e.__doc__)
+                print("An exception occurred: {}".format(e))
+                # Giving up as prerequisites can't be found
+                print(
+                    "\nFactory Geometry Nodes workspace needed for POV text centric"
+                    "\nworkspace to activate when POV is set as default renderer"
+                )
+        finally:
+            # Create POVWIN like editor (text oriented editing)
+            if (
+                "POV-Ed" not in available_workspaces
+                and "Geometry Nodes" in available_workspaces
+            ):
+                wsp = available_workspaces.get("Geometry Nodes")
+                context = bpy.context
+                if context.scene.render.engine == "POVRAY_RENDER" and wsp is not None:
+                    bpy.ops.workspace.duplicate({"workspace": wsp})
+                    available_workspaces["Geometry Nodes.001"].name = "POV-Ed"
+                    # May be already done, but explicitly make this workspace the active one
+                    context.window.workspace = available_workspaces["POV-Ed"]
+                    pov_screen = available_workspaces["POV-Ed"].screens[0]
+                    pov_workspace = pov_screen.areas
+                    pov_window = context.window
+                    # override = bpy.context.copy()  # crashes
+                    override = {}
+                    properties_area = pov_workspace[0]
+                    nodes_to_3dview_area = pov_workspace[1]
+                    view3d_to_text_area = pov_workspace[2]
+                    spreadsheet_to_console_area = pov_workspace[3]
+
+                    try:
+                        nodes_to_3dview_area.ui_type = "VIEW_3D"
+                        override["window"] = pov_window
+                        override["screen"] = bpy.context.screen
+                        override["area"] = nodes_to_3dview_area
+                        override["region"] = nodes_to_3dview_area.regions[-1]
+                        bpy.ops.screen.space_type_set_or_cycle(
+                            override, "INVOKE_DEFAULT", space_type="VIEW_3D"
+                        )
+                        space = nodes_to_3dview_area.spaces.active
+                        space.region_3d.view_perspective = "CAMERA"
+
+                        override["window"] = pov_window
+                        override["screen"] = bpy.context.screen
+                        override["area"] = view3d_to_text_area
+                        override["region"] = view3d_to_text_area.regions[-1]
+                        override["scene"] = bpy.context.scene
+                        override["space_data"] = view3d_to_text_area.spaces.active
+                        bpy.ops.screen.space_type_set_or_cycle(
+                            override, "INVOKE_DEFAULT", space_type="TEXT_EDITOR"
+                        )
+                        view3d_to_text_area.spaces.active.show_region_ui = True
+
+                        spreadsheet_to_console_area.ui_type = "CONSOLE"
+                        override["window"] = pov_window
+                        override["screen"] = bpy.context.screen
+                        override["area"] = spreadsheet_to_console_area
+                        override["region"] = spreadsheet_to_console_area.regions[-1]
+                        bpy.ops.screen.space_type_set_or_cycle(
+                            override, "INVOKE_DEFAULT", space_type="CONSOLE"
+                        )
+                        space = properties_area.spaces.active
+                        space.context = "RENDER"
+                        bpy.ops.workspace.reorder_to_front(
+                            {"workspace": available_workspaces["POV-Ed"]}
+                        )
+                    except AttributeError:
+                        # In case necessary area types lack in existing blend files
+                        pass
+    if "POV-Mo" not in available_workspaces:
+        try:
+            if all(tab not in available_workspaces for tab in ["Rendering", "POV-Mo"]):
+                bpy.ops.workspace.append_activate(
+                    idname="Rendering",
+                    filepath=os.path.join(bpy.utils.user_resource("CONFIG"), "startup.blend"),
+                )
+        except BaseException as e:
+            print(e.__doc__)
+            print("An exception occurred: {}".format(e))
+            try:
+                # Last resort: try to import from the blender templates
+                for p in Path(next(bpy.utils.app_template_paths())).rglob("startup.blend"):
+                    bpy.ops.workspace.append_activate(idname="Rendering", filepath=str(p))
+            except BaseException as e:
+                print(e.__doc__)
+                print("An exception occurred: {}".format(e))
+                # Giving up
+                print(
+                    "\nFactory 'Rendering' workspace needed for POV GUI centric"
+                    "\nworkspace to activate when POV is set as default renderer"
+                )
+        finally:
+            # Create Moray like workspace (GUI oriented editing)
+            if "POV-Mo" not in available_workspaces and "Rendering" in available_workspaces:
+                wsp1 = available_workspaces.get("Rendering")
+                context = bpy.context
+                if context.scene.render.engine == "POVRAY_RENDER" and wsp1 is not None:
+                    bpy.ops.workspace.duplicate({"workspace": wsp1})
+                    available_workspaces["Rendering.001"].name = "POV-Mo"
+                    # Already done it would seem, but explicitly make this workspace the active one
+                    context.window.workspace = available_workspaces["POV-Mo"]
+                    pov_screen = available_workspaces["POV-Mo"].screens[0]
+                    pov_workspace = pov_screen.areas
+                    pov_window = context.window
+                    # override = bpy.context.copy()  # crashes
+                    override = {}
+                    properties_area = pov_workspace[0]
+                    image_editor_to_view3d_area = pov_workspace[2]
+
+                    try:
+                        image_editor_to_view3d_area.ui_type = "VIEW_3D"
+                        override["window"] = pov_window
+                        override["screen"] = bpy.context.screen
+                        override["area"] = image_editor_to_view3d_area
+                        override["region"] = image_editor_to_view3d_area.regions[-1]
+                        bpy.ops.screen.space_type_set_or_cycle(
+                            override, "INVOKE_DEFAULT", space_type="VIEW_3D"
+                        )
+                        space = (
+                            image_editor_to_view3d_area.spaces.active
+                        )  # Uncomment For non quad view
+                        space.region_3d.view_perspective = (
+                            "CAMERA"  # Uncomment For non quad view
+                        )
+                        space.show_region_toolbar = True
+                        # bpy.ops.view3d.camera_to_view(override)  # Uncomment For non quad view ?
+                        for num, reg in enumerate(image_editor_to_view3d_area.regions):
+                            if reg.type != "view3d":
+                                override["region"] = image_editor_to_view3d_area.regions[num]
+                        bpy.ops.screen.region_quadview(override)  # Comment out for non quad
+                        propspace = properties_area.spaces.active
+                        propspace.context = "MATERIAL"
+                        bpy.ops.workspace.reorder_to_front(
+                            {"workspace": available_workspaces["POV-Mo"]}
+                        )
+                    except (AttributeError, TypeError):
+                        # In case necessary types lack in existing blend files
+                        pass
+                    # available_workspaces.update()
+
+
+# class TextureTypePanel(TextureButtonsPanel):
+
+# @classmethod
+# def poll(cls, context):
+# tex = context.texture
+# engine = context.scene.render.engine
+# return tex and ((tex.type == cls.tex_type and not tex.use_nodes) and (engine in cls.COMPAT_ENGINES))
+
+
+def register():
+    update_files.register()
+    render_gui.register()
+    scenography_gui.register()
+    model_gui.register()
+    shading_gui.register()
+    texturing_gui.register()
+    nodes.register()
+    scripting_gui.register()
+
+    if pov_centric_moray_like_workspace not in bpy.app.handlers.load_post:
+        bpy.app.handlers.load_post.append(pov_centric_moray_like_workspace)
+
+
+def unregister():
+    if pov_centric_moray_like_workspace in bpy.app.handlers.load_post:
+        bpy.app.handlers.load_post.remove(pov_centric_moray_like_workspace)
+
+    scripting_gui.unregister()
+    nodes.unregister()
+    texturing_gui.unregister()
+    shading_gui.unregister()
+    model_gui.unregister()
+    scenography_gui.unregister()
+    render_gui.unregister()
+    update_files.unregister()
diff --git a/render_povray/update_files.py b/render_povray/update_files.py
index cc736a98f..3c0aa28a1 100755
--- a/render_povray/update_files.py
+++ b/render_povray/update_files.py
@@ -40,7 +40,7 @@ def update2_0_0_9():
     # XXX We could also store the new name, but as it is just the same without leading pov_ ...
     # Get default values of pov scene props.
     old_sce_props = {
-        k: getattr(bpy.types.Scene, k)[1].get('default', None)
+        k: getattr(bpy.types.Scene, k)[1].get("default", None)
         for k in [
             "pov_tempfiles_enable",
             "pov_deletefiles_enable",
@@ -90,7 +90,7 @@ def update2_0_0_9():
 
     # Get default values of pov material props.
     old_mat_props = {
-        k: getattr(bpy.types.Material, k)[1].get('default', None)
+        k: getattr(bpy.types.Material, k)[1].get("default", None)
         for k in [
             "pov_irid_enable",
             "pov_mirror_use_IOR",
@@ -113,7 +113,7 @@ def update2_0_0_9():
 
     # Get default values of pov texture props.
     old_tex_props = {
-        k: getattr(bpy.types.Texture, k)[1].get('default', None)
+        k: getattr(bpy.types.Texture, k)[1].get("default", None)
         for k in [
             "pov_tex_gamma_enable",
             "pov_tex_gamma_value",
@@ -123,7 +123,7 @@ def update2_0_0_9():
 
     # Get default values of pov object props.
     old_obj_props = {
-        k: getattr(bpy.types.Object, k)[1].get('default', None)
+        k: getattr(bpy.types.Object, k)[1].get("default", None)
         for k in [
             "pov_importance_value",
             "pov_collect_photons",
@@ -133,7 +133,7 @@ def update2_0_0_9():
 
     # Get default values of pov camera props.
     old_cam_props = {
-        k: getattr(bpy.types.Camera, k)[1].get('default', None)
+        k: getattr(bpy.types.Camera, k)[1].get("default", None)
         for k in [
             "pov_dof_enable",
             "pov_dof_aperture",
@@ -147,8 +147,7 @@ def update2_0_0_9():
 
     # Get default values of pov text props.
     old_txt_props = {
-        k: getattr(bpy.types.Text, k)[1].get('default', None)
-        for k in ["pov_custom_code"]
+        k: getattr(bpy.types.Text, k)[1].get("default", None) for k in ["pov_custom_code"]
     }
 
     # -----------------------------------------------------------------------------
@@ -195,7 +194,7 @@ class RenderCopySettings(bpy.types.Operator):
 
     bl_idname = "scene.pov_update_properties"
     bl_label = "PovRay render: Update to script v0.0.9"
-    bl_option = {'REGISTER'}
+    bl_option = {"REGISTER"}
 
     @classmethod
     def poll(cls, context):
@@ -203,7 +202,7 @@ class RenderCopySettings(bpy.types.Operator):
 
     def execute(self, context):
         update2_0_0_9()
-        return {'FINISHED'}
+        return {"FINISHED"}
 
 
 def register():
@@ -274,13 +273,13 @@ def register():
     Scene.pov_media_color = FloatVectorProperty(
         name="Media Color",
         description="The atmospheric media color",
-        subtype='COLOR',
+        subtype="COLOR",
         precision=4,
         step=0.01,
         min=0,
         soft_max=1,
         default=(0.001, 0.001, 0.001),
-        options={'ANIMATABLE'},
+        options={"ANIMATABLE"},
     )
 
     Scene.pov_baking_enable = BoolProperty(
@@ -561,12 +560,6 @@ def register():
         default=False,
     )
 
-    Mat.pov_mirror_metallic = BoolProperty(
-        name="Metallic Reflection",
-        description="mirror reflections get colored as diffuse (for metallic materials)",
-        default=False,
-    )
-
     Mat.pov_conserve_energy = BoolProperty(
         name="Conserve Energy",
         description="Light transmitted is more correctly reduced by mirror reflections, also the sum of diffuse and translucency gets reduced below one ",
@@ -606,13 +599,13 @@ def register():
     Mat.pov_interior_fade_color = FloatVectorProperty(
         name="Fade Color",
         description="Color of filtered attenuation for transparent materials",
-        subtype='COLOR',
+        subtype="COLOR",
         precision=4,
         step=0.01,
         min=0.0,
         soft_max=1.0,
         default=(0, 0, 0),
-        options={'ANIMATABLE'},
+        options={"ANIMATABLE"},
     )
 
     Mat.pov_caustics_enable = BoolProperty(
@@ -839,7 +832,6 @@ def unregister():
     del Scene.pov_comments_enable
     del Mat.pov_irid_enable
     del Mat.pov_mirror_use_IOR
-    del Mat.pov_mirror_metallic
     del Mat.pov_conserve_energy
     del Mat.pov_irid_amount
     del Mat.pov_irid_thickness
diff --git a/render_povray/df3_library.py b/render_povray/voxel_lib.py
old mode 100755
new mode 100644
similarity index 100%
rename from render_povray/df3_library.py
rename to render_povray/voxel_lib.py
-- 
GitLab