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+)XX8 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