diff --git a/render_povray/__init__.py b/render_povray/__init__.py index 7a6332ee8568d986c10e21df1db72bac1f823317..fded5cffe93ab49bbebfa0c4b1e74f3c4b549001 100644 --- a/render_povray/__init__.py +++ b/render_povray/__init__.py @@ -25,16 +25,16 @@ Scene Description Language. The script has been split in as few files as possible : ___init__.py : - Initialize variables + Initialize properties update_files.py - Update new variables to values from older API. This file needs an update. + Update new variables to values from older API. This file needs an update ui.py : - Provide property buttons for the user to set up the variables. + Provide property buttons for the user to set up the variables primitives.py : - Display some POV native primitives in 3D view for input and output. + Display some POV native primitives in 3D view for input and output shading.py Translate shading properties to declared textures at the top of a pov file @@ -50,7 +50,7 @@ render.py : 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 IDEs such as povwin or QTPOV presets : Material (sss) apple.py ; chicken.py ; cream.py ; Ketchup.py ; marble.py ; @@ -64,11 +64,14 @@ Blender stand up to other POV IDEs such as povwin or QTPOV. 01_Clear_Blue_Sky.py ; 02_Partly_Hazy_Sky.py ; 03_Overcast_Sky.py ; 04_Cartoony_Sky.py ; 05_Under_Water.py ; Light - 01_(5400K)_Direct_Sun.py ; 02_(5400K)_High_Noon_Sun.py ; + 01_(4800K)_Direct_Sun.py ; + 02_(5400K)_High_Noon_Sun.py ; 03_(6000K)_Daylight_Window.py ; 04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py ; - 05_(4000K)_100W_Metal_Halide.py ; 06_(3200K)_100W_Quartz_Halogen.py ; - 07_(2850K)_100w_Tungsten.py ; 08_(2600K)_40w_Tungsten.py ; + 05_(4000K)_100W_Metal_Halide.py ; + 06_(3200K)_100W_Quartz_Halogen.py ; + 07_(2850K)_100w_Tungsten.py ; + 08_(2600K)_40w_Tungsten.py ; 09_(5000K)_75W_Full_Spectrum_Fluorescent_T12.py ; 10_(4300K)_40W_Vintage_Fluorescent_T12.py ; 11_(5000K)_18W_Standard_Fluorescent_T8 ; @@ -78,10 +81,13 @@ Blender stand up to other POV IDEs such as povwin or QTPOV. 15_(3200K)_40W_Induction_Fluorescent.py ; 16_(2100K)_150W_High_Pressure_Sodium.py ; 17_(1700K)_135W_Low_Pressure_Sodium.py ; - 18_(6800K)_175W_Mercury_Vapor.py ; 19_(5200K)_700W_Carbon_Arc.py ; - 20_(6500K)_15W_LED_Spot.py ; 21_(2700K)_7W_OLED_Panel.py ; + 18_(6800K)_175W_Mercury_Vapor.py ; + 19_(5200K)_700W_Carbon_Arc.py ; + 20_(6500K)_15W_LED_Spot.py ; + 21_(2700K)_7W_OLED_Panel.py ; 22_(30000K)_40W_Black_Light_Fluorescent.py ; - 23_(30000K)_40W_Black_Light_Bulb.py; 24_(1850K)_Candle.py + 23_(30000K)_40W_Black_Light_Bulb.py; + 24_(1850K)_Candle.py templates: abyss.pov ; biscuit.pov ; bsp_Tango.pov ; chess2.pov ; cornell.pov ; diffract.pov ; diffuse_back.pov ; float5 ; @@ -98,20 +104,23 @@ bl_info = { "Bastien Montagne, " "Constantin Rahn, " "Silvio Falcinelli", - "version": (0, 1, 0), + "version": (0, 1, 1), "blender": (2, 81, 0), "location": "Render Properties > Render Engine > Persistence of Vision", "description": "Persistence of Vision integration for blender", "doc_url": "{BLENDER_MANUAL_URL}/addons/render/povray.html", "category": "Render", + "warning": "Under active development, seeking co-maintainer(s)", } if "bpy" in locals(): import importlib importlib.reload(ui) + importlib.reload(nodes) importlib.reload(render) importlib.reload(shading) + importlib.reload(primitives) importlib.reload(update_files) else: @@ -121,13 +130,18 @@ else: import nodeitems_utils # for Nodes from nodeitems_utils import NodeCategory, NodeItem # for Nodes from bl_operators.presets import AddPresetBase - from bpy.types import AddonPreferences, PropertyGroup + from bpy.types import ( + AddonPreferences, + PropertyGroup, + NodeSocket, + ) + from bpy.props import ( + FloatVectorProperty, StringProperty, BoolProperty, IntProperty, FloatProperty, - FloatVectorProperty, EnumProperty, PointerProperty, CollectionProperty, @@ -137,39 +151,94 @@ else: def string_strip_hyphen(name): - """Remove hyphen characters from a string to avoid POV errors.""" + """Remove hyphen characters from a string to avoid POV errors""" return name.replace("-", "") +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': + 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.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 + +def brush_texture_update(self, context): + + """Brush texture rolldown must show active slot texture props""" + idblock = pov_context_tex_datablock(context) + if idblock is not None: + #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: + # Switch paint brush to active texture so slot and settings remain contextual + 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] def active_texture_name_from_uilist(self, context): - mat = context.scene.view_layers["View Layer"].objects.active.active_material - index = mat.pov.active_texture_index - name = mat.pov_texture_slots[index].name - newname = mat.pov_texture_slots[index].texture - tex = bpy.data.textures[name] - tex.name = newname - mat.pov_texture_slots[index].name = newname + + idblock = pov_context_tex_datablock(context) + #mat = context.view_layer.objects.active.active_material + if idblock is not None: + index = idblock.pov.active_texture_index + name = idblock.pov_texture_slots[index].name + newname = idblock.pov_texture_slots[index].texture + tex = bpy.data.textures[name] + tex.name = newname + idblock.pov_texture_slots[index].name = newname def active_texture_name_from_search(self, context): - mat = context.scene.view_layers["View Layer"].objects.active.active_material - index = mat.pov.active_texture_index - name = mat.pov_texture_slots[index].texture_search + """Texture rolldown to change the data linked by an existing texture""" + idblock = pov_context_tex_datablock(context) + #mat = context.view_layer.objects.active.active_material + if idblock is not None: + index = idblock.pov.active_texture_index + slot = idblock.pov_texture_slots[index] + name = slot.texture_search + try: tex = bpy.data.textures[name] - mat.pov_texture_slots[index].name = name - mat.pov_texture_slots[index].texture = name + slot.name = name + slot.texture = name + # Switch paint brush to this texture so settings remain contextual + #bpy.context.tool_settings.image_paint.brush.texture = tex + #bpy.context.tool_settings.image_paint.brush.mask_texture = tex except: pass + ############################################################################### # Scene POV properties. ############################################################################### class RenderPovSettingsScene(PropertyGroup): - """Declare scene level properties controllable in UI and translated to POV.""" + """Declare scene level properties controllable in UI and translated to POV""" # Linux SDL-window enable sdl_window_enable: BoolProperty( @@ -770,7 +839,7 @@ class RenderPovSettingsScene(PropertyGroup): name="Error Bound", description="One of the two main speed/quality tuning values, " "lower values are more accurate", - min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=1.8 + min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=10.0 ) radio_gray_threshold: FloatProperty( @@ -837,14 +906,15 @@ class RenderPovSettingsScene(PropertyGroup): name="Pretrace Start", description="Fraction of the screen width which sets the size of the " "blocks in the mosaic preview first pass", - min=0.01, max=1.00, soft_min=0.02, soft_max=1.0, default=0.08 + min=0.005, max=1.00, soft_min=0.02, soft_max=1.0, default=0.04 ) - + # XXX TODO set automatically to pretrace_end = 8 / max (image_width, image_height) + # for non advanced mode radio_pretrace_end: FloatProperty( name="Pretrace End", description="Fraction of the screen width which sets the size of the blocks " "in the mosaic preview last pass", - min=0.000925, max=1.00, soft_min=0.01, soft_max=1.00, default=0.04, precision=3 + min=0.000925, max=1.00, soft_min=0.01, soft_max=1.00, default=0.004, precision=3 ) ############################################################################### @@ -856,19 +926,28 @@ class MaterialTextureSlot(PropertyGroup): bl_idname="pov_texture_slots", bl_description="Texture_slots from Blender-2.79", + # Adding a "real" texture datablock as property is not possible + # (or at least not easy through a dynamically populated EnumProperty). + # That's why we'll use a prop_search() UILayout function in ui.py. + # So we'll assign the name of the needed texture datablock to the below StringProperty. texture : StringProperty(update=active_texture_name_from_uilist) - texture_search : StringProperty(update=active_texture_name_from_search) + # and use another temporary StringProperty to change the linked data + texture_search : StringProperty( + name="", + update = active_texture_name_from_search, + description = "Browse Texture to be linked", + ) alpha_factor: FloatProperty( name="Alpha", description="Amount texture affects alpha", - default = 0.0, + default = 1.0, ) ambient_factor: FloatProperty( name="", description="Amount texture affects ambient", - default = 0.0, + default = 1.0, ) bump_method: EnumProperty( @@ -897,49 +976,49 @@ class MaterialTextureSlot(PropertyGroup): density_factor: FloatProperty( name="", description="Amount texture affects density", - default = 0.0, + default = 1.0, ) diffuse_color_factor: FloatProperty( name="", description="Amount texture affects diffuse color", - default = 0.0, + default = 1.0, ) diffuse_factor: FloatProperty( name="", description="Amount texture affects diffuse reflectivity", - default = 0.0, + default = 1.0, ) displacement_factor: FloatProperty( name="", description="Amount texture displaces the surface", - default = 0.0, + default = 0.2, ) emission_color_factor: FloatProperty( name="", description="Amount texture affects emission color", - default = 0.0, + default = 1.0, ) emission_factor: FloatProperty( name="", description="Amount texture affects emission", - default = 0.0, + default = 1.0, ) emit_factor: FloatProperty( name="", description="Amount texture affects emission", - default = 0.0, + default = 1.0, ) hardness_factor: FloatProperty( name="", description="Amount texture affects hardness", - default = 0.0, + default = 1.0, ) mapping: EnumProperty( @@ -985,13 +1064,13 @@ class MaterialTextureSlot(PropertyGroup): mirror_factor: FloatProperty( name="", description="Amount texture affects mirror color", - default = 0.0, + default = 1.0, ) normal_factor: FloatProperty( name="", description="Amount texture affects normal values", - default = 0.0, + default = 1.0, ) normal_map_space: EnumProperty( @@ -1013,39 +1092,65 @@ class MaterialTextureSlot(PropertyGroup): raymir_factor: FloatProperty( name="", description="Amount texture affects ray mirror", - default = 0.0, + default = 1.0, ) reflection_color_factor: FloatProperty( name="", description="Amount texture affects color of out-scattered light", - default = 0.0, + default = 1.0, ) reflection_factor: FloatProperty( name="", description="Amount texture affects brightness of out-scattered light", - default = 0.0, + default = 1.0, ) scattering_factor: FloatProperty( name="", description="Amount texture affects scattering", - default = 0.0, + default = 1.0, ) specular_color_factor: FloatProperty( name="", description="Amount texture affects specular color", - default = 0.0, + default = 1.0, ) specular_factor: FloatProperty( name="", description="Amount texture affects specular reflectivity", - default = 0.0, + default = 1.0, + ) + + offset: FloatVectorProperty( + name="Offset", + description=("Fine tune of the texture mapping X, Y and Z locations "), + precision=4, + step=0.1, + soft_min= -100.0, + soft_max=100.0, + default=(0.0,0.0,0.0), + options={'ANIMATABLE'}, + subtype='TRANSLATION', ) + scale: FloatVectorProperty( + name="Size", + subtype='XYZ', + size=3, + description="Set scaling for the texture’s X, Y and Z sizes ", + precision=4, + step=0.1, + soft_min= -100.0, + soft_max=100.0, + default=(1.0,1.0,1.0), + options={'ANIMATABLE'}, + ) + + texture_coords: EnumProperty( name="", description="", @@ -1068,13 +1173,13 @@ class MaterialTextureSlot(PropertyGroup): translucency_factor: FloatProperty( name="", description="Amount texture affects translucency", - default = 0.0, + default = 1.0, ) transmission_color_factor: FloatProperty( name="", description="Amount texture affects result color after light has been scattered/absorbed", - default = 0.0, + default = 1.0, ) use: BoolProperty( @@ -1095,6 +1200,12 @@ class MaterialTextureSlot(PropertyGroup): default = False, ) + use_interpolation: BoolProperty( + name="", + description="Interpolates pixels using selected filter ", + default = False, + ) + use_map_alpha: BoolProperty( name="", description="Causes the texture to affect the alpha value", @@ -1110,7 +1221,7 @@ class MaterialTextureSlot(PropertyGroup): use_map_color_diffuse: BoolProperty( name="", description="Causes the texture to affect basic color of the material", - default = False, + default = True, ) use_map_color_emission: BoolProperty( @@ -1234,7 +1345,7 @@ class MaterialTextureSlot(PropertyGroup): ) -#######################################" +####################################### blend_factor: FloatProperty( name="Blend", @@ -1328,10 +1439,10 @@ bpy.types.ID.texture_context = EnumProperty( default = 'MATERIAL', ) -bpy.types.ID.active_texture_index = IntProperty( - name = "Index for texture_slots", - default = 0, -) +# bpy.types.ID.active_texture_index = IntProperty( + # name = "Index for texture_slots", + # default = 0, +# ) class RenderPovSettingsMaterial(PropertyGroup): """Declare material level properties controllable in UI and translated to POV.""" @@ -1360,6 +1471,7 @@ class RenderPovSettingsMaterial(PropertyGroup): active_texture_index: IntProperty( name = "Index for texture_slots", default = 0, + update = brush_texture_update ) transparency_method: EnumProperty( @@ -2123,7 +2235,7 @@ class MaterialRaytraceTransparency(PropertyGroup): gloss_samples: IntProperty( name="Samples", - description="Number of cone samples averaged for blurry refractions", + description="frequency of the noise sample used for blurry refractions", min=0, max=1024, default=18 ) @@ -2205,8 +2317,8 @@ class MaterialRaytraceMirror(PropertyGroup): ) gloss_samples: IntProperty( - name="Samples", - description="Number of cone samples averaged for blurry reflections", + name="Noise", + description="Frequency of the noise pattern bumps averaged for blurry reflections", min=0, max=1024, default=18, ) @@ -3223,7 +3335,7 @@ class MaterialStrandSettings(PropertyGroup): # Povray Nodes ############################################################################### -class PovraySocketUniversal(bpy.types.NodeSocket): +class PovraySocketUniversal(NodeSocket): bl_idname = 'PovraySocketUniversal' bl_label = 'Povray Socket' value_unlimited: bpy.props.FloatProperty(default=0.0) @@ -3276,7 +3388,7 @@ class PovraySocketUniversal(bpy.types.NodeSocket): def draw_color(self, context, node): return (1, 0, 0, 1) -class PovraySocketFloat_0_1(bpy.types.NodeSocket): +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) @@ -3289,7 +3401,7 @@ class PovraySocketFloat_0_1(bpy.types.NodeSocket): def draw_color(self, context, node): return (0.5, 0.7, 0.7, 1) -class PovraySocketFloat_0_10(bpy.types.NodeSocket): +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) @@ -3304,7 +3416,7 @@ class PovraySocketFloat_0_10(bpy.types.NodeSocket): def draw_color(self, context, node): return (0.65, 0.65, 0.65, 1) -class PovraySocketFloat_10(bpy.types.NodeSocket): +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) @@ -3319,7 +3431,7 @@ class PovraySocketFloat_10(bpy.types.NodeSocket): def draw_color(self, context, node): return (0.65, 0.65, 0.65, 1) -class PovraySocketFloatPositive(bpy.types.NodeSocket): +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) @@ -3331,7 +3443,7 @@ class PovraySocketFloatPositive(bpy.types.NodeSocket): def draw_color(self, context, node): return (0.045, 0.005, 0.136, 1) -class PovraySocketFloat_000001_10(bpy.types.NodeSocket): +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) @@ -3343,7 +3455,7 @@ class PovraySocketFloat_000001_10(bpy.types.NodeSocket): def draw_color(self, context, node): return (1, 0, 0, 1) -class PovraySocketFloatUnlimited(bpy.types.NodeSocket): +class PovraySocketFloatUnlimited(NodeSocket): bl_idname = 'PovraySocketFloatUnlimited' bl_label = 'Povray Socket' default_value: bpy.props.FloatProperty(default = 0.0) @@ -3355,7 +3467,7 @@ class PovraySocketFloatUnlimited(bpy.types.NodeSocket): def draw_color(self, context, node): return (0.7, 0.7, 1, 1) -class PovraySocketInt_1_9(bpy.types.NodeSocket): +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) @@ -3367,7 +3479,7 @@ class PovraySocketInt_1_9(bpy.types.NodeSocket): def draw_color(self, context, node): return (1, 0.7, 0.7, 1) -class PovraySocketInt_0_256(bpy.types.NodeSocket): +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) @@ -3380,7 +3492,7 @@ class PovraySocketInt_0_256(bpy.types.NodeSocket): return (0.5, 0.5, 0.5, 1) -class PovraySocketPattern(bpy.types.NodeSocket): +class PovraySocketPattern(NodeSocket): bl_idname = 'PovraySocketPattern' bl_label = 'Povray Socket' @@ -3417,7 +3529,7 @@ class PovraySocketPattern(bpy.types.NodeSocket): def draw_color(self, context, node): return (1, 1, 1, 1) -class PovraySocketColor(bpy.types.NodeSocket): +class PovraySocketColor(NodeSocket): bl_idname = 'PovraySocketColor' bl_label = 'Povray Socket' @@ -3434,7 +3546,7 @@ class PovraySocketColor(bpy.types.NodeSocket): def draw_color(self, context, node): return (1, 1, 0, 1) -class PovraySocketColorRGBFT(bpy.types.NodeSocket): +class PovraySocketColorRGBFT(NodeSocket): bl_idname = 'PovraySocketColorRGBFT' bl_label = 'Povray Socket' @@ -3452,7 +3564,7 @@ class PovraySocketColorRGBFT(bpy.types.NodeSocket): def draw_color(self, context, node): return (1, 1, 0, 1) -class PovraySocketTexture(bpy.types.NodeSocket): +class PovraySocketTexture(NodeSocket): bl_idname = 'PovraySocketTexture' bl_label = 'Povray Socket' default_value: bpy.props.IntProperty() @@ -3464,7 +3576,7 @@ class PovraySocketTexture(bpy.types.NodeSocket): -class PovraySocketTransform(bpy.types.NodeSocket): +class PovraySocketTransform(NodeSocket): bl_idname = 'PovraySocketTransform' bl_label = 'Povray Socket' default_value: bpy.props.IntProperty(min=0,max=255,default=0) @@ -3474,7 +3586,7 @@ class PovraySocketTransform(bpy.types.NodeSocket): def draw_color(self, context, node): return (99/255, 99/255, 199/255, 1) -class PovraySocketNormal(bpy.types.NodeSocket): +class PovraySocketNormal(NodeSocket): bl_idname = 'PovraySocketNormal' bl_label = 'Povray Socket' default_value: bpy.props.IntProperty(min=0,max=255,default=0) @@ -3484,7 +3596,7 @@ class PovraySocketNormal(bpy.types.NodeSocket): def draw_color(self, context, node): return (0.65, 0.65, 0.65, 1) -class PovraySocketSlope(bpy.types.NodeSocket): +class PovraySocketSlope(NodeSocket): bl_idname = 'PovraySocketSlope' bl_label = 'Povray Socket' default_value: bpy.props.FloatProperty(min = 0.0, max = 1.0) @@ -3500,7 +3612,7 @@ class PovraySocketSlope(bpy.types.NodeSocket): def draw_color(self, context, node): return (0, 0, 0, 1) -class PovraySocketMap(bpy.types.NodeSocket): +class PovraySocketMap(NodeSocket): bl_idname = 'PovraySocketMap' bl_label = 'Povray Socket' default_value: bpy.props.StringProperty() @@ -3774,7 +3886,7 @@ class RenderPovSettingsTexture(PropertyGroup): ('checker', "Checker", "", 'PLUGIN', 34), ('hexagon', "Hexagon", "", 'PLUGIN', 35), ('object', "Mesh", "", 'PLUGIN', 36), - ('emulator', "Internal Emulator", "", 'PLUG', 37) + ('emulator', "Blender Type Emulator", "", 'SCRIPTPLUGINS', 37) ), default='emulator', ) @@ -5167,7 +5279,7 @@ class RenderPovSettingsWorld(PropertyGroup): items=( ('MATERIAL', "", "Show material textures", "MATERIAL",0), # "Show material textures" ('WORLD', "", "Show world textures", "WORLD",1), # "Show world textures" - ('LAMP', "", "Show lamp textures", "LIGHT",2), # "Show lamp textures" + ('LIGHT', "", "Show lamp textures", "LIGHT",2), # "Show lamp textures" ('PARTICLES', "", "Show particles textures", "PARTICLES",3), # "Show particles textures" ('LINESTYLE', "", "Show linestyle textures", "LINE_DATA",4), # "Show linestyle textures" ('OTHER', "", "Show other data textures", "TEXTURE_DATA",5), # "Show other data textures" @@ -5215,12 +5327,27 @@ class RenderPovSettingsWorld(PropertyGroup): ) active_texture_index: IntProperty( name = "Index for texture_slots", - default = 0 + default = 0, + update = brush_texture_update ) - class WorldTextureSlot(PropertyGroup): - """Declare world texture slot properties controllable in UI and translated to POV.""" + """Declare world texture slot level properties for UI and translated to POV.""" + + bl_idname="pov_texture_slots", + bl_description="Texture_slots from Blender-2.79", + + # Adding a "real" texture datablock as property is not possible + # (or at least not easy through a dynamically populated EnumProperty). + # That's why we'll use a prop_search() UILayout function in ui.py. + # So we'll assign the name of the needed texture datablock to the below StringProperty. + texture : StringProperty(update=active_texture_name_from_uilist) + # and use another temporary StringProperty to change the linked data + texture_search : StringProperty( + name="", + update = active_texture_name_from_search, + description = "Browse Texture to be linked", + ) blend_factor: FloatProperty( name="Blend", @@ -5241,6 +5368,31 @@ class WorldTextureSlot(PropertyGroup): default="", ) + offset: FloatVectorProperty( + name="Offset", + description=("Fine tune of the texture mapping X, Y and Z locations "), + precision=4, + step=0.1, + soft_min= -100.0, + soft_max=100.0, + default=(0.0,0.0,0.0), + options={'ANIMATABLE'}, + subtype='TRANSLATION', + ) + + scale: FloatVectorProperty( + name="Size", + subtype='XYZ', + size=3, + description="Set scaling for the texture’s X, Y and Z sizes ", + precision=4, + step=0.1, + soft_min= -100.0, + soft_max=100.0, + default=(1.0,1.0,1.0), + options={'ANIMATABLE'}, + ) + texture_coords: EnumProperty( name="Coordinates", description="Texture coordinates used to map the texture onto the background", @@ -5308,7 +5460,7 @@ for i in range(18): # length of world texture slots class MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist(bpy.types.UIList): # 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): ob = data @@ -5385,11 +5537,44 @@ class PovrayPreferences(AddonPreferences): subtype='FILE_PATH', ) + use_sounds: BoolProperty( + name="Use Sound", + description="Signaling end of the render process at various" + "stages can help if you're away from monitor", + default=False, + ) + + # TODO: Auto find POV sound directory as it does for binary + # 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', + ) + + filepath_parse_error_sound: StringProperty( + 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", + subtype='FILE_PATH', + ) + + #shall we not move this to UI file? 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') classes = ( @@ -5451,7 +5636,7 @@ def register(): bpy.types.Light.pov = PointerProperty(type=RenderPovSettingsLight) bpy.types.World.pov = PointerProperty(type=RenderPovSettingsWorld) bpy.types.Material.pov_texture_slots = CollectionProperty(type=MaterialTextureSlot) - bpy.types.World.texture_slots = CollectionProperty(type=WorldTextureSlot) + bpy.types.World.pov_texture_slots = CollectionProperty(type=WorldTextureSlot) bpy.types.Text.pov = PointerProperty(type=RenderPovSettingsText) @@ -5468,6 +5653,7 @@ def unregister(): del bpy.types.Camera.pov del bpy.types.Light.pov del bpy.types.World.pov + del bpy.types.World.pov_texture_slots del bpy.types.Material.pov_texture_slots del bpy.types.Text.pov diff --git a/render_povray/nodes.py b/render_povray/nodes.py index be535db30c0270361b36a9be018bfc2e6852cf23..bbdb97545683d0fc425c258717aada67afd42e62 100644 --- a/render_povray/nodes.py +++ b/render_povray/nodes.py @@ -21,7 +21,14 @@ import bpy from bpy.utils import register_class -from bpy.types import Node, ShaderNodeTree, CompositorNodeTree, TextureNodeTree#, NodeSocket +from bpy.types import ( + Node, + ShaderNodeTree, + CompositorNodeTree, + TextureNodeTree, + #NodeSocket, + Operator, + ) from bpy.props import ( StringProperty, BoolProperty, @@ -1025,7 +1032,7 @@ class TextureOutputNode(Node, TextureNodeTree): ################################################################################## -class NODE_OT_iso_add(bpy.types.Operator): +class NODE_OT_iso_add(Operator): bl_idname = "pov.nodeisoadd" bl_label = "Create iso props" @@ -1042,7 +1049,7 @@ class NODE_OT_iso_add(bpy.types.Operator): isonode.label = ob.name return {'FINISHED'} -class NODE_OT_map_create(bpy.types.Operator): +class NODE_OT_map_create(Operator): bl_idname = "node.map_create" bl_label = "Create map" @@ -1067,7 +1074,7 @@ class NODE_OT_map_create(bpy.types.Operator): mat = context.object.active_material layout.prop(mat.pov,"inputs_number") -class NODE_OT_povray_node_texture_map_add(bpy.types.Operator): +class NODE_OT_povray_node_texture_map_add(Operator): bl_idname = "pov.nodetexmapadd" bl_label = "Texture map" @@ -1091,7 +1098,7 @@ class NODE_OT_povray_node_texture_map_add(bpy.types.Operator): return {'FINISHED'} -class NODE_OT_povray_node_output_add(bpy.types.Operator): +class NODE_OT_povray_node_output_add(Operator): bl_idname = "pov.nodeoutputadd" bl_label = "Output" @@ -1105,7 +1112,7 @@ class NODE_OT_povray_node_output_add(bpy.types.Operator): tmap.label="Output" return {'FINISHED'} -class NODE_OT_povray_node_layered_add(bpy.types.Operator): +class NODE_OT_povray_node_layered_add(Operator): bl_idname = "pov.nodelayeredadd" bl_label = "Layered material" @@ -1116,7 +1123,7 @@ class NODE_OT_povray_node_layered_add(bpy.types.Operator): tmap.label="Layered material" return {'FINISHED'} -class NODE_OT_povray_input_add(bpy.types.Operator): +class NODE_OT_povray_input_add(Operator): bl_idname = "pov.nodeinputadd" bl_label = "Add entry" @@ -1141,7 +1148,7 @@ class NODE_OT_povray_input_add(bpy.types.Operator): return {'FINISHED'} -class NODE_OT_povray_input_remove(bpy.types.Operator): +class NODE_OT_povray_input_remove(Operator): bl_idname = "pov.nodeinputremove" bl_label = "Remove input" @@ -1159,7 +1166,7 @@ class NODE_OT_povray_input_remove(bpy.types.Operator): els.remove(el) return {'FINISHED'} -class NODE_OT_povray_image_open(bpy.types.Operator): +class NODE_OT_povray_image_open(Operator): bl_idname = "pov.imageopen" bl_label = "Open" @@ -1181,7 +1188,7 @@ class NODE_OT_povray_image_open(bpy.types.Operator): return {'FINISHED'} -# class TEXTURE_OT_povray_open_image(bpy.types.Operator): +# class TEXTURE_OT_povray_open_image(Operator): # bl_idname = "pov.openimage" # bl_label = "Open Image" @@ -1204,7 +1211,7 @@ class NODE_OT_povray_image_open(bpy.types.Operator): # view_layer.update() # return {'FINISHED'} -class PovrayPatternNode(bpy.types.Operator): +class PovrayPatternNode(Operator): bl_idname = "pov.patternnode" bl_label = "Pattern" @@ -1259,7 +1266,7 @@ class PovrayPatternNode(bpy.types.Operator): context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} -class UpdatePreviewMaterial(bpy.types.Operator): +class UpdatePreviewMaterial(Operator): '''Operator update preview material''' bl_idname = "node.updatepreview" bl_label = "Update preview" @@ -1283,7 +1290,7 @@ class UpdatePreviewMaterial(bpy.types.Operator): context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} -class UpdatePreviewKey(bpy.types.Operator): +class UpdatePreviewKey(Operator): '''Operator update preview keymap''' bl_idname = "wm.updatepreviewkey" bl_label = "Activate RMB" diff --git a/render_povray/primitives.py b/render_povray/primitives.py index a9d68d448c30bc94c80ca19f465386ed709eb622..6d864220306446ffa6c31d1d2ca813c7e4c89f2c 100644 --- a/render_povray/primitives.py +++ b/render_povray/primitives.py @@ -26,7 +26,7 @@ from bpy_extras.io_utils import ImportHelper from bpy_extras import object_utils from bpy.utils import register_class from math import atan, pi, degrees, sqrt, cos, sin - +from bpy.types import Operator from bpy.props import ( StringProperty, @@ -41,6 +41,7 @@ from bpy.props import ( from mathutils import Vector, Matrix + # import collections @@ -60,7 +61,7 @@ def pov_define_mesh(mesh, verts, edges, faces, name, hide_geometry=True): return mesh -class POVRAY_OT_lathe_add(bpy.types.Operator): +class POVRAY_OT_lathe_add(Operator): """Add the representation of POV lathe using a screw modifier.""" bl_idname = "pov.addlathe" @@ -212,7 +213,7 @@ def pov_superellipsoid_define(context, op, ob): bpy.ops.object.mode_set(mode="OBJECT") -class POVRAY_OT_superellipsoid_add(bpy.types.Operator): +class POVRAY_OT_superellipsoid_add(Operator): """Add the representation of POV superellipsoid using the pov_superellipsoid_define() function.""" bl_idname = "pov.addsuperellipsoid" @@ -286,7 +287,7 @@ class POVRAY_OT_superellipsoid_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_superellipsoid_update(bpy.types.Operator): +class POVRAY_OT_superellipsoid_update(Operator): """Update the superellipsoid. Delete its previous proxy geometry and rerun pov_superellipsoid_define() function @@ -455,7 +456,7 @@ def pov_supertorus_define(context, op, ob): ob.pov.st_edit = st_edit -class POVRAY_OT_supertorus_add(bpy.types.Operator): +class POVRAY_OT_supertorus_add(Operator): """Add the representation of POV supertorus using the pov_supertorus_define() function.""" bl_idname = "pov.addsupertorus" @@ -530,7 +531,7 @@ class POVRAY_OT_supertorus_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_supertorus_update(bpy.types.Operator): +class POVRAY_OT_supertorus_update(Operator): """Update the supertorus. Delete its previous proxy geometry and rerun pov_supetorus_define() function @@ -566,7 +567,7 @@ class POVRAY_OT_supertorus_update(bpy.types.Operator): ######################################################################################################### -class POVRAY_OT_loft_add(bpy.types.Operator): +class POVRAY_OT_loft_add(Operator): """Create the representation of POV loft using Blender curves.""" bl_idname = "pov.addloft" @@ -695,7 +696,7 @@ class POVRAY_OT_loft_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_plane_add(bpy.types.Operator): +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 @@ -725,7 +726,7 @@ class POVRAY_OT_plane_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_box_add(bpy.types.Operator): +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 @@ -796,7 +797,7 @@ def pov_cylinder_define(context, op, ob, radius, loc, loc_cap): bpy.ops.object.shade_smooth() -class POVRAY_OT_cylinder_add(bpy.types.Operator): +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.""" @@ -846,7 +847,7 @@ class POVRAY_OT_cylinder_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_cylinder_update(bpy.types.Operator): +class POVRAY_OT_cylinder_update(Operator): """Update the POV cylinder. Delete its previous proxy geometry and rerun pov_cylinder_define() function @@ -932,7 +933,7 @@ def pov_sphere_define(context, op, ob, loc): bpy.ops.object.mode_set(mode="OBJECT") -class POVRAY_OT_sphere_add(bpy.types.Operator): +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.""" @@ -989,7 +990,7 @@ class POVRAY_OT_sphere_add(bpy.types.Operator): # return {'FINISHED'} -class POVRAY_OT_sphere_update(bpy.types.Operator): +class POVRAY_OT_sphere_update(Operator): """Update the POV sphere. Delete its previous proxy geometry and rerun pov_sphere_define() function @@ -1084,7 +1085,7 @@ def pov_cone_define(context, op, ob): ob.pov.cone_cap_z = zc -class POVRAY_OT_cone_add(bpy.types.Operator): +class POVRAY_OT_cone_add(Operator): """Add the representation of POV cone using pov_cone_define() function.""" bl_idname = "pov.cone_add" @@ -1139,7 +1140,7 @@ class POVRAY_OT_cone_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_cone_update(bpy.types.Operator): +class POVRAY_OT_cone_update(Operator): """Update the POV cone. Delete its previous proxy geometry and rerun pov_cone_define() function @@ -1177,7 +1178,7 @@ class POVRAY_OT_cone_update(bpy.types.Operator): ########################################ISOSURFACES################################## -class POVRAY_OT_isosurface_box_add(bpy.types.Operator): +class POVRAY_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 @@ -1207,7 +1208,7 @@ class POVRAY_OT_isosurface_box_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_isosurface_sphere_add(bpy.types.Operator): +class POVRAY_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 @@ -1238,7 +1239,7 @@ class POVRAY_OT_isosurface_sphere_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_sphere_sweep_add(bpy.types.Operator): +class POVRAY_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 @@ -1264,7 +1265,7 @@ class POVRAY_OT_sphere_sweep_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_blob_add(bpy.types.Operator): +class POVRAY_OT_blob_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 @@ -1284,7 +1285,7 @@ class POVRAY_OT_blob_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_rainbow_add(bpy.types.Operator): +class POVRAY_OT_rainbow_add(Operator): """Add the representation of POV rainbow using a Blender spot light. Rainbows indeed propagate along a visibility cone. @@ -1334,7 +1335,7 @@ class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper): bl_idname = "pov.addheightfield" bl_label = "Height Field" - bl_description = "Add Height Field " + bl_description = "Add Height Field" bl_options = {'REGISTER', 'UNDO'} # XXX Keep it in sync with __init__'s hf Primitive @@ -1470,7 +1471,7 @@ def pov_torus_define(context, op, ob): bpy.ops.object.mode_set(mode="OBJECT") -class POVRAY_OT_torus_add(bpy.types.Operator): +class POVRAY_OT_torus_add(Operator): """Add the representation of POV torus using using pov_torus_define() function.""" bl_idname = "pov.addtorus" @@ -1503,7 +1504,7 @@ class POVRAY_OT_torus_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_torus_update(bpy.types.Operator): +class POVRAY_OT_torus_update(Operator): """Update the POV torus. Delete its previous proxy geometry and rerun pov_torus_define() function @@ -1536,7 +1537,7 @@ class POVRAY_OT_torus_update(bpy.types.Operator): ################################################################################### -class POVRAY_OT_prism_add(bpy.types.Operator): +class POVRAY_OT_prism_add(Operator): """Add the representation of POV prism using using an extruded curve.""" bl_idname = "pov.addprism" @@ -1662,7 +1663,7 @@ def pov_parametric_define(context, op, ob): bpy.ops.object.mode_set(mode="OBJECT") -class POVRAY_OT_parametric_add(bpy.types.Operator): +class POVRAY_OT_parametric_add(Operator): """Add the representation of POV parametric surfaces using pov_parametric_define() function.""" bl_idname = "pov.addparametric" @@ -1698,7 +1699,7 @@ class POVRAY_OT_parametric_add(bpy.types.Operator): return {'FINISHED'} -class POVRAY_OT_parametric_update(bpy.types.Operator): +class POVRAY_OT_parametric_update(Operator): """Update the representation of POV parametric surfaces. Delete its previous proxy geometry and rerun pov_parametric_define() function @@ -1731,7 +1732,7 @@ class POVRAY_OT_parametric_update(bpy.types.Operator): ####################################################################### -class POVRAY_OT_shape_polygon_to_circle_add(bpy.types.Operator): +class POVRAY_OT_shape_polygon_to_circle_add(Operator): """Add the proxy mesh for POV Polygon to circle lofting macro""" bl_idname = "pov.addpolygontocircle" diff --git a/render_povray/render.py b/render_povray/render.py index 6efe7291540584d588ef93c98b4fdfd51f5b3d95..f9de22e48ffbc54b91bb1c6989f651377b67908e 100644 --- a/render_povray/render.py +++ b/render_povray/render.py @@ -24,6 +24,10 @@ import os import sys import time from math import atan, pi, degrees, sqrt, cos, sin +#################### +## Faster mesh export +import numpy as np +#################### import re import random import platform # @@ -69,6 +73,7 @@ def imageFormat(imgF): def imgMap(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': image_map = "map_type 0 " elif ts.mapping == 'SPHERE': @@ -82,9 +87,9 @@ def imgMap(ts): # image_map = " map_type 3 " # elif ts.mapping=="?": # image_map = " map_type 4 " - if ts.texture.use_interpolation: + if ts.use_interpolation: # Available if image sampling class reactivated? image_map += " interpolate 2 " - if ts.texture.extension == 'CLIP': + if texdata.extension == 'CLIP': image_map += " once " # image_map += "}" # if ts.mapping=='CUBE': @@ -110,12 +115,12 @@ def imgMapTransforms(ts): image_map_transforms = ( "scale <%.4g,%.4g,%.4g> translate <%.4g,%.4g,%.4g>" % ( - 1.0 / ts.scale.x, - 1.0 / ts.scale.y, - 1.0 / ts.scale.z, - 0.5 - (0.5 / ts.scale.x) - (ts.offset.x), - 0.5 - (0.5 / ts.scale.y) - (ts.offset.y), - ts.offset.z, + ts.scale[0], + ts.scale[1], + ts.scale[2], + ts.offset[0], + ts.offset[1], + ts.offset[2], ) ) # image_map_transforms = (" translate <-0.5,-0.5,0.0> scale <%.4g,%.4g,%.4g> translate <%.4g,%.4g,%.4g>" % \ @@ -137,6 +142,7 @@ def imgMapTransforms(ts): def imgMapBG(wts): """Translate world mapping from Blender UI to POV syntax and return that string.""" + tex = bpy.data.textures[wts.texture] image_mapBG = "" # texture_coords refers to the mapping of world textures: if wts.texture_coords == 'VIEW' or wts.texture_coords == 'GLOBAL': @@ -146,9 +152,9 @@ def imgMapBG(wts): elif wts.texture_coords == 'TUBE': image_mapBG = " map_type 2 " - if wts.texture.use_interpolation: + if tex.use_interpolation: image_mapBG += " interpolate 2 " - if wts.texture.extension == 'CLIP': + if tex.extension == 'CLIP': image_mapBG += " once " # image_mapBG += "}" # if wts.mapping == 'CUBE': @@ -386,6 +392,7 @@ def write_object_modifiers(scene, ob, File): 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 # file = filename @@ -690,7 +697,7 @@ def write_pov(filename, scene=None, info_callback=None): tabWrite("right <%s, 0, 0>\n" % -Qsize) tabWrite("up <0, 1, 0>\n") tabWrite( - "angle %f\n" % (360.0 * atan(16.0 / camera.data.lens) / pi) + "angle %f\n" % ( 2 * atan(camera.data.sensor_width / 2 / camera.data.lens) * 180.0 / pi ) ) tabWrite( @@ -737,6 +744,7 @@ def write_pov(filename, scene=None, info_callback=None): def exportLamps(lamps): """Translate lights from Blender UI to POV syntax and write to exported file.""" + # Incremented after each lamp export to declare its target # currently used for Fresnel diffuse shader as their slope vector: global lampCount @@ -2346,6 +2354,64 @@ def write_pov(filename, scene=None, info_callback=None): def exportMeshes(scene, sel, csg): """write all meshes as POV mesh2{} syntax to exported file """ + #some numpy functions to speed up mesh export + + # 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 verex 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 ... @@ -2684,6 +2750,7 @@ def write_pov(filename, scene=None, info_callback=None): pmaterial = ob.material_slots[ pSys.settings.material - 1 ].material + #XXX Todo: replace by pov_(Particles?)_texture_slot for th in pmaterial.texture_slots: if th and th.use: if ( @@ -3411,6 +3478,8 @@ def write_pov(filename, scene=None, info_callback=None): ob.pov.v_max, ) ) + # Previous to 3.8 default max_gradient 1.0 was too slow + tabWrite("max_gradient 0.001\n") if ob.pov.contained_by == "sphere": tabWrite("contained_by { sphere{0, 2} }\n") else: @@ -3593,6 +3662,14 @@ def write_pov(filename, scene=None, info_callback=None): me.calc_loop_triangles() me_materials = me.materials me_faces = me.loop_triangles[:] + ## numpytest + #me_looptris = me.loops + + ## 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: # tabWrite("\n//dummy sphere to represent empty mesh location\n") # tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname) @@ -4092,6 +4169,7 @@ def write_pov(filename, scene=None, info_callback=None): else: shading.writeTextureInfluence( + using_uberpov, mater, materialNames, LocalMaterialNames, @@ -4581,89 +4659,91 @@ def write_pov(filename, scene=None, info_callback=None): for ( t ) in ( - world.texture_slots + world.pov_texture_slots ): # risk to write several sky_spheres but maybe ok. - if t and t.texture.type is not None: + if t: + tex = bpy.data.textures[t.texture] + if tex.type is not None: worldTexCount += 1 - # XXX No enable checkbox for world textures yet (report it?) - # if t and t.texture.type == 'IMAGE' and t.use: - if t and t.texture.type == 'IMAGE': - image_filename = path_image(t.texture.image) - if t.texture.image.filepath != image_filename: - t.texture.image.filepath = image_filename - if image_filename != "" and t.use_map_blend: - texturesBlend = 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 - # mappingBlend = (" 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': - mappingBlend = "" - 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 - - mappingBlend = ( - "scale 2 scale <%.4g,%.4g,%.4g> translate -1 " - "translate <%.4g,%.4g,%.4g> rotate<0,0,0> " + # 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: + texturesBlend = 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 + # mappingBlend = (" 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': + mappingBlend = "" + 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 + + mappingBlend = ( + "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, + ) + ) + + # 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. + tabWrite("sky_sphere {\n") + tabWrite("pigment {\n") + tabWrite( + "image_map{%s \"%s\" %s}\n" % ( - (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, + imageFormat(texturesBlend), + texturesBlend, + imgMapBG(t_blend), ) ) - - # 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. - tabWrite("sky_sphere {\n") - tabWrite("pigment {\n") - tabWrite( - "image_map{%s \"%s\" %s}\n" - % ( - imageFormat(texturesBlend), - texturesBlend, - imgMapBG(t_blend), + tabWrite("}\n") + tabWrite("%s\n" % (mappingBlend)) + # The following layered pigment opacifies to black over the texture for + # transmit below 1 or otherwise adds to itself + tabWrite( + "pigment {rgb 0 transmit %s}\n" % (tex.intensity) ) - ) - tabWrite("}\n") - tabWrite("%s\n" % (mappingBlend)) - # The following layered pigment opacifies to black over the texture for - # transmit below 1 or otherwise adds to itself - tabWrite( - "pigment {rgb 0 transmit %s}\n" % (t.texture.intensity) - ) - tabWrite("}\n") - # tabWrite("scale 2\n") - # tabWrite("translate -1\n") + tabWrite("}\n") + # tabWrite("scale 2\n") + # tabWrite("translate -1\n") # For only Background gradient @@ -4910,7 +4990,8 @@ def write_pov(filename, scene=None, info_callback=None): "//--Exported with POV-Ray exporter for Blender--\n" "//----------------------------------------------\n\n" ) - file.write("#version 3.7;\n") + file.write("#version 3.7;\n") #Switch below as soon as 3.8 beta gets easy linked + #file.write("#version 3.8;\n") file.write( "#declare Default_texture = texture{pigment {rgb 0.8} " "finish {brilliance 3.8} }\n\n" @@ -5066,7 +5147,11 @@ def write_pov(filename, scene=None, info_callback=None): if comments: file.write("//--Mesh objects--\n") + + #tbefore = time.time() exportMeshes(scene, sel, csg) + #totime = time.time() - tbefore + #print("exportMeshes took" + str(totime)) # What follow used to happen here: # exportCamera() @@ -5097,7 +5182,7 @@ def write_pov_ini( y = int(render.resolution_y * render.resolution_percentage * 0.01) file = open(filename_ini, "w") - file.write("Version=3.7\n") + file.write("Version=3.8\n") # write povray text stream to temporary file of same name with _log suffix # file.write("All_File='%s'\n" % filename_log) # DEBUG.OUT log if none specified: @@ -5797,15 +5882,106 @@ class PovrayRender(bpy.types.RenderEngine): # print(filename_log) #bring the pov log to blender console with proper path? with open( - self._temp_file_log + self._temp_file_log, + encoding='utf-8' ) as f: # The with keyword automatically closes the file when you are done - print(f.read()) + 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') + 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 != None: + scr = win.screen + for area in scr.areas: + if area.type == 'CONSOLE': + #context override + #ctx = {'window': win, 'screen': scr, 'area':area}#bpy.context.copy() + ctx = {} + ctx['area'] = area + ctx['region'] = area.regions[-1] + ctx['space_data'] = area.spaces.active + ctx['screen'] = scr#C.screen + ctx['window'] = win + + #bpy.ops.console.banner(ctx, text = "Hello world") + bpy.ops.console.clear_line(ctx) + stdmsg = msg.split('\n') #XXX todo , test and see + for i in stdmsg: + bpy.ops.console.insert(ctx, text = i) 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 + + if sys.platform[:3] == "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 + + #Does Linux support say command? + elif sys.platform[:3] != "win" : + finished_render_message = "\'Render completed\'" + # We don't want the say command to block Python, + # so we add an ampersand after the message + os.system("say %s &" % (finished_render_message)) ################################################################################## #################################Operators######################################## @@ -5833,7 +6009,7 @@ class RenderPovTexturePreview(Operator): outputPrevFile = os.path.join(preview_dir, texPrevName) ##################### ini ########################################## fileIni = open("%s" % iniPrevFile, "w") - fileIni.write('Version=3.7\n') + fileIni.write('Version=3.8\n') fileIni.write('Input_File_Name="%s"\n' % inputPrevFile) fileIni.write('Output_File_Name="%s.png"\n' % outputPrevFile) fileIni.write('Library_Path="%s"\n' % preview_dir) diff --git a/render_povray/shading.py b/render_povray/shading.py index ac1f923f6703ddf205c493ed3e3c68903163e093..a3f907dceda4311ed73cc408aa20042c815f030e 100644 --- a/render_povray/shading.py +++ b/render_povray/shading.py @@ -155,7 +155,7 @@ def writeMaterial(using_uberpov, DEF_MAT_NAME, scene, tabWrite, safety, comments elif Level == 1: if (material.pov.specular_shader == 'COOKTORR' or material.pov.specular_shader == 'PHONG'): - tabWrite("phong %.3g\n" % (material.pov.specular_intensity/5)) + tabWrite("phong 0\n")#%.3g\n" % (material.pov.specular_intensity/5)) tabWrite("phong_size %.3g\n" % (material.pov.specular_hardness /3.14)) # POV-Ray 'specular' keyword corresponds to a Blinn model, without the ior. @@ -183,8 +183,10 @@ def writeMaterial(using_uberpov, DEF_MAT_NAME, scene, tabWrite, safety, comments # specular for some values. tabWrite("brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8)) elif Level == 3: - tabWrite("specular %.3g\n" % ((material.pov.specular_intensity*material.pov.specular_color.v)*5)) - tabWrite("roughness %.3g\n" % (1.1/material.pov.specular_hardness)) + # Spec must be Max at Level 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? + tabWrite("specular %.3g\n" % ((material.pov.specular_intensity*material.pov.specular_color.v)*(255* slot.specular_factor))) + tabWrite("roughness %.3g\n" % (1/material.pov.specular_hardness)) tabWrite("diffuse %.3g %.3g\n" % (frontDiffuse, backDiffuse)) tabWrite("ambient %.3g\n" % material.pov.ambient) @@ -265,12 +267,12 @@ def writeMaterial(using_uberpov, DEF_MAT_NAME, scene, tabWrite, safety, comments if material: special_texture_found = False - idx = -1 + tmpidx = -1 for t in material.pov_texture_slots: - idx += 1 + tmpidx += 1 # index = material.pov.active_texture_index - slot = material.pov_texture_slots[idx] # [index] - povtex = slot.name + slot = material.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: @@ -777,7 +779,7 @@ def exportPattern(texture, string_strip_hyphen): return(texStrg) -def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, lampCount, +def writeTextureInfluence(using_uberpov, mater, materialNames, LocalMaterialNames, path_image, lampCount, imageFormat, imgMap, imgMapTransforms, tabWrite, comments, string_strip_hyphen, safety, col, os, preview_dir, unpacked_images): """Translate Blender texture influences to various POV texture tricks and write to pov file.""" @@ -805,14 +807,15 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, texturesNorm = "" texturesAlpha = "" #proceduralFlag=False + tmpidx = -1 for t in mater.pov_texture_slots: - idx = -1 - for t in mater.pov_texture_slots: - idx += 1 - # index = mater.pov.active_texture_index - slot = mater.pov_texture_slots[idx] # [index] - povtex = slot.name - tex = bpy.data.textures[povtex] + + + 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) @@ -876,11 +879,13 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, # IMAGE SEQUENCE ENDS imgGamma = "" if image_filename: + texdata = bpy.data.textures[t.texture] if t.use_map_color_diffuse: texturesDif = image_filename # colvalue = t.default_value # UNUSED t_dif = t - if t_dif.texture.pov.tex_gamma_enable: + print (texdata) + if texdata.pov.tex_gamma_enable: imgGamma = (" gamma %.3g " % t_dif.texture.pov.tex_gamma_value) if t.use_map_specular or t.use_map_raymir: texturesSpec = image_filename @@ -913,6 +918,7 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, if mater.pov.replacement_text != "": tabWrite("%s\n" % mater.pov.replacement_text) ################################################################################# + # XXX TODO: replace by new POV MINNAERT rather than aoi if mater.pov.diffuse_shader == 'MINNAERT': tabWrite("\n") tabWrite("aoi\n") @@ -1056,12 +1062,36 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, mappingNor =imgMapTransforms(t_nor) if texturesNorm and texturesNorm.startswith("PAT_"): - tabWrite("normal{function{f%s(x,y,z).grey} bump_size %.4g %s}\n" %(texturesNorm, t_nor.normal_factor, mappingNor)) + tabWrite("normal{function{f%s(x,y,z).grey} bump_size %.4g %s}\n" %(texturesNorm, ( - t_nor.normal_factor * 9.5), mappingNor)) else: - tabWrite("normal {uv_mapping bump_map " \ - "{%s \"%s\" %s bump_size %.4g }%s}\n" % \ + tabWrite("normal {\n") + # XXX TODO: fix and propagate the micro normals reflection blur below to non textured materials + if (mater.pov_raytrace_mirror.use and mater.pov_raytrace_mirror.gloss_factor < 1.0 and not using_uberpov): + tabWrite("average\n") + tabWrite("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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[1.0 ") # Proceed with user bump... + tabWrite("uv_mapping bump_map " \ + "{%s \"%s\" %s bump_size %.4g }%s" % \ (imageFormat(texturesNorm), texturesNorm, imgMap(t_nor), - t_nor.normal_factor, mappingNor)) + ( - t_nor.normal_factor * 9.5), mappingNor)) + # ...Then close its last entry and the the normal_map itself + if (mater.pov_raytrace_mirror.use and mater.pov_raytrace_mirror.gloss_factor < 1.0 and not using_uberpov): + tabWrite("]}}\n") + else: + tabWrite("]}\n") if texturesSpec != "": tabWrite("]\n") ##################Second index for mapping specular max value############### @@ -1094,6 +1124,35 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, # Level 3 is full specular tabWrite("finish {%s}\n" % (safety(material_finish, Level=3))) + if mater.pov_raytrace_mirror.use and mater.pov_raytrace_mirror.gloss_factor < 1.0 and not using_uberpov: + tabWrite("normal {\n") + tabWrite("average\n") + tabWrite("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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 texturesNorm != "": + tabWrite("[1.0 ") # Blurry reflection or not Proceed with user bump in either case... + tabWrite("uv_mapping bump_map " \ + "{%s \"%s\" %s bump_size %.4g }%s]\n" % \ + (imageFormat(texturesNorm), texturesNorm, imgMap(t_nor), + ( - t_nor.normal_factor * 9.5), mappingNor)) + # ...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 not using_uberpov: + tabWrite("}}\n") + else: + tabWrite("}\n") elif colored_specular_found: # Level 1 is no specular tabWrite("finish {%s}\n" % (safety(material_finish, Level=1))) @@ -1166,11 +1225,36 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, mappingNor =imgMapTransforms(t_nor) if texturesNorm and texturesNorm.startswith("PAT_"): - tabWrite("normal{function{f%s(x,y,z).grey} bump_size %.4g %s}\n" %(texturesNorm, t_nor.normal_factor, mappingNor)) + tabWrite("normal{function{f%s(x,y,z).grey} bump_size %.4g %s}\n" %(texturesNorm, ( - t_nor.normal_factor * 9.5), mappingNor)) else: - tabWrite("normal {uv_mapping bump_map {%s \"%s\" %s bump_size %.4g }%s}\n" % \ + tabWrite("normal {\n") + # XXX TODO: fix and propagate the micro normals reflection blur below to non textured materials + if mater.pov_raytrace_mirror.use and mater.pov_raytrace_mirror.gloss_factor < 1.0 and not using_uberpov: + tabWrite("average\n") + tabWrite("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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[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 + tabWrite("[1.0 ") # Blurry reflection or not Proceed with user bump in either case... + tabWrite("uv_mapping bump_map " \ + "{%s \"%s\" %s bump_size %.4g }%s]\n" % \ (imageFormat(texturesNorm), texturesNorm, imgMap(t_nor), - t_nor.normal_factor, mappingNor)) + ( - t_nor.normal_factor * 9.5), mappingNor)) + # ...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 not using_uberpov: + tabWrite("}}\n") + else: + tabWrite("}\n") if texturesSpec != "" and mater.pov.replacement_text == "": tabWrite("]\n") @@ -1201,12 +1285,12 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, # Write another layered texture using invisible diffuse and metallic trick # to emulate colored specular highlights special_texture_found = False - idx = -1 + tmpidx = -1 for t in mater.pov_texture_slots: - idx += 1 + tmpidx += 1 # index = mater.pov.active_texture_index - slot = mater.pov_texture_slots[idx] # [index] - povtex = slot.name + slot = mater.pov_texture_slots[tmpidx] # [index] + povtex = slot.texture # slot.name tex = bpy.data.textures[povtex] if(t and t.use and ((tex.type == 'IMAGE' and tex.image) or tex.type != 'IMAGE') and (t.use_map_specular or t.use_map_raymir)): @@ -1240,7 +1324,7 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, if image_filename: if t.use_map_normal: texturesNorm = image_filename - # colvalue = t.normal_factor/10 # UNUSED + # colvalue = t.normal_factor/10 # UNUSED XXX *-9.5 ! #textNormName=tex.image.name + ".normal" #was the above used? --MR t_nor = t @@ -1248,13 +1332,13 @@ def writeTextureInfluence(mater, materialNames, LocalMaterialNames, path_image, tabWrite("normal{function" \ "{f%s(x,y,z).grey} bump_size %.4g}\n" % \ (texturesNorm, - t_nor.normal_factor)) + ( - t_nor.normal_factor * 9.5))) else: tabWrite("normal {uv_mapping bump_map " \ "{%s \"%s\" %s bump_size %.4g }%s}\n" % \ (imageFormat(texturesNorm), texturesNorm, imgMap(t_nor), - t_nor.normal_factor, + ( - t_nor.normal_factor * 9.5), mappingNor)) tabWrite("}\n") # THEN IT CAN CLOSE LAST LAYER OF TEXTURE diff --git a/render_povray/ui.py b/render_povray/ui.py index e15a93741fecd34813391205d5c702554b916e14..9ac79067b04e21d148908e9971e0f1eea21f5dcc 100644 --- a/render_povray/ui.py +++ b/render_povray/ui.py @@ -22,11 +22,15 @@ import bpy import sys # really import here and in render.py? import os # really import here and in render.py? +import addon_utils +from time import sleep from os.path import isfile +from bpy.app.handlers import persistent from bl_operators.presets import AddPresetBase from bpy.utils import register_class, unregister_class from bpy.types import ( Operator, + Menu, UIList, Panel, Brush, @@ -43,17 +47,29 @@ from bl_ui import properties_output for member in dir(properties_output): subclass = getattr(properties_output, member) try: - subclass.COMPAT_ENGINES.add('POVRAY') + subclass.COMPAT_ENGINES.add('POVRAY_RENDER') except: pass del properties_output +from bl_ui import properties_freestyle +for member in dir(properties_freestyle): + subclass = getattr(properties_freestyle, member) + try: + if not (subclass.bl_space_type == 'PROPERTIES' + and subclass.bl_context == "render"): + subclass.COMPAT_ENGINES.add('POVRAY_RENDER') + #subclass.bl_parent_id = "RENDER_PT_POV_filter" + except: + pass +del properties_freestyle + from bl_ui import properties_view_layer for member in dir(properties_view_layer): subclass = getattr(properties_view_layer, member) try: - subclass.COMPAT_ENGINES.add('POVRAY') + subclass.COMPAT_ENGINES.add('POVRAY_RENDER') except: pass del properties_view_layer @@ -242,19 +258,139 @@ for member in dir( pass del properties_particle -# Example of wrapping every class 'as is' -from bl_ui import properties_output -for member in dir(properties_output): - subclass = getattr(properties_output, member) - try: - subclass.COMPAT_ENGINES.add('POVRAY_RENDER') - except: - pass -del properties_output + +#Use some delay? +#https://blenderartists.org/t/restrictdata-object-has-no-attribute-scenes-how-to-avoid-it/579885/4 +############# POV-Centric WORSPACE ############# +@persistent +def povCentricWorkspace(dummy): + """Set up a POV centric Workspace if addon was left activate from previous session + + 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 the + bpy.app.handlers.persistent decorator is used (@persistent) above. + """ + + #bpy.context.scene.render.engine = 'POVRAY_RENDER' + wsp = bpy.data.workspaces.get('Scripting') + context = bpy.context + if wsp is not None: + new_wsp = bpy.ops.workspace.duplicate({'workspace': wsp}) + bpy.data.workspaces['Scripting.001'].name='POV' + # Already done it would seem, but explicitly make this workspaces the active one + context.window.workspace = bpy.data.workspaces['POV'] + pov_screen = bpy.data.workspaces['POV'].screens[0] + pov_workspace = pov_screen.areas + + + override = bpy.context.copy() + + for area in pov_workspace: + if area.type == 'VIEW_3D': + for region in [r for r in area.regions if r.type == 'WINDOW']: + for space in area.spaces: + if space.type == 'VIEW_3D': + #override['screen'] = pov_screen + override['area'] = area + override['region']= region + #bpy.data.workspaces['POV'].screens[0].areas[6].spaces[0].width = 333 # Read only, how do we set ? + #This has a glitch: + #bpy.ops.screen.area_move(override, x=(area.x + area.width), y=(area.y + 5), delta=100) + #bpy.ops.screen.area_move(override, x=(area.x + 5), y=area.y, delta=-100) + + bpy.ops.screen.space_type_set_or_cycle(override, space_type = 'TEXT_EDITOR') + #bpy.ops.screen.region_scale(override) + #bpy.ops.screen.region_scale() + break + + elif area.type == 'CONSOLE': + for region in [r for r in area.regions if r.type == 'WINDOW']: + for space in area.spaces: + if space.type == 'CONSOLE': + #override['screen'] = pov_screen + override['area'] = area + override['region']= region + bpy.ops.screen.space_type_set_or_cycle(override, space_type = 'INFO') + + break + elif area.type == 'INFO': + for region in [r for r in area.regions if r.type == 'WINDOW']: + for space in area.spaces: + if space.type == 'INFO': + #override['screen'] = pov_screen + override['area'] = area + override['region']= region + bpy.ops.screen.space_type_set_or_cycle(override, space_type = 'CONSOLE') + + break + + elif area.type == 'TEXT_EDITOR': + for region in [r for r in area.regions if r.type == 'WINDOW']: + for space in area.spaces: + if space.type == 'TEXT_EDITOR': + #override['screen'] = pov_screen + override['area'] = area + override['region']= region + #bpy.ops.screen.space_type_set_or_cycle(space_type='VIEW_3D') + #space.type = 'VIEW_3D' + bpy.ops.screen.space_type_set_or_cycle(override, space_type = 'VIEW_3D') + + #bpy.ops.screen.area_join(override, cursor=(area.x, area.y + area.height)) + + break + + + if area.type == 'VIEW_3D': + for region in [r for r in area.regions if r.type == 'WINDOW']: + for space in area.spaces: + if space.type == 'VIEW_3D': + #override['screen'] = pov_screen + override['area'] = area + override['region']= region + bpy.ops.screen.region_quadview(override) + space.region_3d.view_perspective = 'CAMERA' + #bpy.ops.screen.space_type_set_or_cycle(override, space_type = 'TEXT_EDITOR') + #bpy.ops.screen.region_quadview(override) + + + -class WORLD_MT_POV_presets(bpy.types.Menu): + + bpy.data.workspaces.update() + # Already outliners but invert both types + pov_workspace[1].spaces[0].display_mode = 'LIBRARIES' + pov_workspace[3].spaces[0].display_mode = 'VIEW_LAYER' + + ''' + for window in bpy.context.window_manager.windows: + for area in [a for a in window.screen.areas if a.type == 'VIEW_3D']: + for region in [r for r in area.regions if r.type == 'WINDOW']: + context_override = { + 'window': window, + 'screen': window.screen, + 'area': area, + 'region': region, + 'space_data': area.spaces.active, + 'scene': bpy.context.scene + } + bpy.ops.view3d.camera_to_view(context_override) + ''' + + + else: + print("default 'Scripting' workspace needed for POV centric Workspace") + + + + + + + +class WORLD_MT_POV_presets(Menu): bl_label = "World Presets" preset_subdir = "pov/world" preset_operator = "script.execute_preset" @@ -262,7 +398,7 @@ class WORLD_MT_POV_presets(bpy.types.Menu): class WORLD_OT_POV_add_preset(AddPresetBase, Operator): - '''Add a World Preset''' + """Add a World Preset""" bl_idname = "object.world_preset_add" bl_label = "Add World Preset" @@ -324,6 +460,15 @@ def check_add_mesh_extra_objects(): return True return False +def check_render_freestyle_svg(): + """Test if Freestyle SVG Exporter addon is activated + + This addon is currently used to generate the SVG lines file + when Freestyle is enabled alongside POV + """ + if "render_freestyle_svg" in bpy.context.preferences.addons.keys(): + return True + return False def locate_docpath(): """POV can be installed with some include files. @@ -373,31 +518,29 @@ def pov_context_tex_datablock(context): """Texture context type recreated as deprecated in blender 2.8""" idblock = context.brush - if idblock and bpy.context.scene.texture_context == 'OTHER': + if idblock and context.scene.texture_context == 'OTHER': return idblock # idblock = bpy.context.active_object.active_material - idblock = bpy.context.scene.view_layers[ - "View Layer" - ].objects.active.active_material - if idblock: + idblock = context.view_layer.objects.active.active_material + if idblock and context.scene.texture_context == 'MATERIAL': return idblock - idblock = context.world - if idblock: + idblock = context.scene.world + if idblock and context.scene.texture_context == 'WORLD': return idblock idblock = context.light - if idblock: + if idblock and context.scene.texture_context == 'LIGHT': return idblock - if context.particle_system: + if context.particle_system and context.scene.texture_context == 'PARTICLES': idblock = context.particle_system.settings return idblock idblock = context.line_style - if idblock: + if idblock and context.scene.texture_context == 'LINESTYLE': return idblock @@ -688,7 +831,7 @@ class LIGHT_PT_POV_light(PovLampButtonsPanel, Panel): draw = properties_data_light.DATA_PT_light.draw -class LIGHT_MT_POV_presets(bpy.types.Menu): +class LIGHT_MT_POV_presets(Menu): """Use this class to define preset menu for pov lights.""" bl_label = "Lamp Presets" @@ -1114,9 +1257,8 @@ class WORLD_PT_POV_mist(WorldButtonsPanel, Panel): class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel): """Use this class to define pov ini settingss buttons.""" - - bl_label = "Start Options" bl_options = {'DEFAULT_CLOSED'} + bl_label = "Auto Start" COMPAT_ENGINES = {'POVRAY_RENDER'} def draw_header(self, context): @@ -1131,6 +1273,7 @@ class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel): ) def draw(self, context): + layout = self.layout scene = context.scene @@ -1143,25 +1286,25 @@ class RENDER_PT_POV_export_settings(RenderButtonsPanel, Panel): col.prop(scene.pov, "command_line_switches", text="") 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") + #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") - col = layout.column() - col.prop(scene.pov, "scene_name", text="Name") - col.prop(scene.pov, "scene_path", text="Path to files") - # col.prop(scene.pov, "scene_path", text="Path to POV-file") - # col.prop(scene.pov, "renderimage_path", text="Path to image") + col = layout.column() + col.prop(scene.pov, "scene_name", text="Name") + col.prop(scene.pov, "scene_path", text="Path to files") + # col.prop(scene.pov, "scene_path", text="Path to POV-file") + # col.prop(scene.pov, "renderimage_path", text="Path to image") - split = layout.split() - split.prop(scene.pov, "indentation_character", text="Indent") - if scene.pov.indentation_character == 'SPACE': - split.prop(scene.pov, "indentation_spaces", text="Spaces") + split = layout.split() + split.prop(scene.pov, "indentation_character", text="Indent") + if scene.pov.indentation_character == 'SPACE': + split.prop(scene.pov, "indentation_spaces", text="Spaces") - row = layout.row() - row.prop(scene.pov, "comments_enable", text="Comments") - row.prop(scene.pov, "list_lf_enable", text="Line breaks in lists") + row = layout.row() + row.prop(scene.pov, "comments_enable", text="Comments") + row.prop(scene.pov, "list_lf_enable", text="Line breaks in lists") class RENDER_PT_POV_render_settings(RenderButtonsPanel, Panel): @@ -1414,7 +1557,7 @@ class RENDER_PT_POV_radiosity(RenderButtonsPanel, Panel): col.prop(scene.pov, "radio_subsurface") -class POV_RADIOSITY_MT_presets(bpy.types.Menu): +class POV_RADIOSITY_MT_presets(Menu): """Use this class to define pov radiosity presets menu.""" bl_label = "Radiosity Presets" @@ -1562,7 +1705,7 @@ class MODIFIERS_PT_POV_modifiers(ModifierButtonsPanel, Panel): col.prop(ob.pov, "inside_vector") -class MATERIAL_MT_POV_sss_presets(bpy.types.Menu): +class MATERIAL_MT_POV_sss_presets(Menu): """Use this class to define pov sss preset menu.""" bl_label = "SSS Presets" @@ -1872,7 +2015,7 @@ class MATERIAL_PT_POV_mirror(MaterialButtonsPanel, Panel): 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_samples", text="Noise") sub.prop(raym, "gloss_anisotropic", text="Anisotropic") @@ -2178,7 +2321,7 @@ class MATERIAL_PT_POV_replacement_text(MaterialButtonsPanel, Panel): col.prop(mat.pov, "replacement_text", text="") -class TEXTURE_MT_POV_specials(bpy.types.Menu): +class TEXTURE_MT_POV_specials(Menu): """Use this class to define pov texture slot operations buttons.""" bl_label = "Texture Specials" @@ -2191,14 +2334,20 @@ class TEXTURE_MT_POV_specials(bpy.types.Menu): layout.operator("texture.slot_paste", icon='PASTEDOWN') -class TEXTURE_UL_POV_texture_slots(bpy.types.UIList): - """Use this class to show pov texture slots list.""" # used? - - COMPAT_ENGINES = {'POVRAY_RENDER'} +class WORLD_TEXTURE_SLOTS_UL_POV_layerlist(UIList): + """Use this class to show pov texture slots list.""" # XXX Not used yet + index: bpy.props.IntProperty(name='index') def draw_item( self, context, layout, data, item, icon, active_data, active_propname ): + world = context.scene.world # .pov + active_data = world.pov + # tex = context.texture #may be needed later? + + # We could write some code to decide which icon to use here... + custom_icon = 'TEXTURE' + ob = data slot = item # ma = slot.name @@ -2220,62 +2369,7 @@ class TEXTURE_UL_POV_texture_slots(bpy.types.UIList): layout.label(text="", icon_value=icon) -''' -class MATERIAL_TEXTURE_SLOTS_UL_List(UIList): - """Texture Slots UIList.""" - - - def draw_item(self, context, layout, material, item, icon, active_data, - material_texture_list_index, index): - material = context.material#.pov - active_data = material - #tex = context.texture #may be needed later? - - - # We could write some code to decide which icon to use here... - custom_icon = 'TEXTURE' - - # Make sure your code supports all 3 layout types - if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.label(item.name, icon = custom_icon) - - elif self.layout_type in {'GRID'}: - layout.alignment = 'CENTER' - layout.label("", icon = custom_icon) -''' - - -class WORLD_TEXTURE_SLOTS_UL_List(UIList): - """Use this class to show pov texture slots list.""" # XXX Not used yet - - def draw_item( - self, - context, - layout, - world, - item, - icon, - active_data, - active_texture_index, - index, - ): - world = context.world # .pov - active_data = world.pov - # tex = context.texture #may be needed later? - - # We could write some code to decide which icon to use here... - custom_icon = 'TEXTURE' - - # Make sure your code supports all 3 layout types - if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.label(item.name, icon=custom_icon) - - elif self.layout_type in {'GRID'}: - layout.alignment = 'CENTER' - layout.label("", icon=custom_icon) - - -class MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist(bpy.types.UIList): +class MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist(UIList): """Use this class to show pov texture slots list.""" # texture_slots: @@ -2304,6 +2398,53 @@ class MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist(bpy.types.UIList): layout.alignment = 'CENTER' layout.label(text="", icon_value=icon) +# Rewrite an existing class to modify. +# register but not unregistered because +# the modified parts concern only POVRAY_RENDER +class TEXTURE_PT_context(TextureButtonsPanel, Panel): + bl_label = "" + bl_context = "texture" + bl_options = {'HIDE_HEADER'} + COMPAT_ENGINES = {'POVRAY_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + @classmethod + def poll(cls, context): + return ( + (context.scene.texture_context + not in('MATERIAL','WORLD','LIGHT','PARTICLES','LINESTYLE') + or context.scene.render.engine != 'POVRAY_RENDER') + ) + def draw(self, context): + layout = self.layout + tex = context.texture + space = context.space_data + pin_id = space.pin_id + use_pin_id = space.use_pin_id + user = context.texture_user + + col = layout.column() + + if not (use_pin_id and isinstance(pin_id, bpy.types.Texture)): + pin_id = None + + if not pin_id: + col.template_texture_user() + + if user or pin_id: + col.separator() + + if pin_id: + col.template_ID(space, "pin_id") + else: + propname = context.texture_user_property.identifier + col.template_ID(user, propname, new="texture.new") + + if tex: + col.separator() + + split = col.split(factor=0.2) + split.label(text="Type") + split.prop(tex, "type", text="") class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): """Use this class to show pov texture context buttons.""" @@ -2316,11 +2457,11 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): def poll(cls, context): engine = context.scene.render.engine return engine in cls.COMPAT_ENGINES - # if not (hasattr(context, "texture_slot") or hasattr(context, "texture_node")): + # if not (hasattr(context, "pov_texture_slot") or hasattr(context, "texture_node")): # return False return ( context.material - or context.world + or context.scene.world or context.light or context.texture or context.line_style @@ -2333,11 +2474,12 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): layout = self.layout scene = context.scene + mat = context.view_layer.objects.active.active_material + wld = context.scene.world + layout.prop(scene, "texture_context", expand=True) - if scene.texture_context == 'MATERIAL': - mat = context.scene.view_layers[ - "View Layer" - ].objects.active.active_material + if scene.texture_context == 'MATERIAL' and mat is not None: + row = layout.row() row.template_list( "MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist", @@ -2353,22 +2495,90 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): col = row.column(align=True) 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' + #col.operator("texture.slot_move", text="", icon='TRIA_DOWN').type = 'DOWN' col.separator() if mat.pov_texture_slots: index = mat.pov.active_texture_index slot = mat.pov_texture_slots[index] - povtex = slot.name + povtex = slot.texture#slot.name tex = bpy.data.textures[povtex] col.prop(tex, 'use_fake_user', text='') - layout.label(text='Find texture:') + #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") + layout.prop_search( - slot, 'texture_search', bpy.data, 'textures', text='' + 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 + + if tex: + layout.separator() + split = layout.split(factor=0.2) + split.label(text="Type") + split.prop(tex, "type", text="") + # 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: + row = layout.row() + row.template_list( + "WORLD_TEXTURE_SLOTS_UL_POV_layerlist", + "", + wld, + "pov_texture_slots", + wld.pov, + "active_texture_index", + rows=2, + maxrows=16, + type="DEFAULT" + ) + col = row.column(align=True) + 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' + #col.operator("texture.slot_move", text="", icon='TRIA_DOWN').type = 'DOWN' + col.separator() + + if wld.pov_texture_slots: + index = wld.pov.active_texture_index + slot = wld.pov_texture_slots[index] + povtex = slot.texture#slot.name + tex = bpy.data.textures[povtex] + 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") + + layout.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 + + if tex: + layout.separator() + split = layout.split(factor=0.2) + split.label(text="Type") + split.prop(tex, "type", text="") # Commented out below is a reminder of what existed in Blender Internal # attributes need to be recreated @@ -2518,7 +2728,7 @@ class TEXTURE_PT_colors(TextureButtonsPanel, Panel): # Texture Slot Panels # -class MATERIAL_OT_POV_texture_slot_add(Operator): +class TEXTURE_OT_POV_texture_slot_add(Operator): """Use this class for the add texture slot button.""" bl_idname = "pov.textureslotadd" @@ -2528,18 +2738,29 @@ class MATERIAL_OT_POV_texture_slot_add(Operator): COMPAT_ENGINES = {'POVRAY_RENDER'} def execute(self, context): - + idblock = pov_context_tex_datablock(context) tex = bpy.data.textures.new(name='Texture', type='IMAGE') - tex.use_fake_user = True - ob = context.scene.view_layers["View Layer"].objects.active - slot = ob.active_material.pov_texture_slots.add() + #tex.use_fake_user = True + #mat = context.view_layer.objects.active.active_material + slot = idblock.pov_texture_slots.add() slot.name = tex.name slot.texture = tex.name + slot.texture_search = tex.name + # Switch paint brush and paint brush mask + # to this texture so settings remain contextual + bpy.context.tool_settings.image_paint.brush.texture = tex + bpy.context.tool_settings.image_paint.brush.mask_texture = tex + idblock.pov.active_texture_index = (len(idblock.pov_texture_slots)-1) + + #for area in bpy.context.screen.areas: + #if area.type in ['PROPERTIES']: + #area.tag_redraw() + return {'FINISHED'} -class MATERIAL_OT_POV_texture_slot_remove(Operator): +class TEXTURE_OT_POV_texture_slot_remove(Operator): """Use this class for the remove texture slot button.""" bl_idname = "pov.textureslotremove" @@ -2549,14 +2770,23 @@ class MATERIAL_OT_POV_texture_slot_remove(Operator): COMPAT_ENGINES = {'POVRAY_RENDER'} def execute(self, context): - pass - # tex = bpy.data.textures.new() - # tex_slot = context.object.active_material.pov_texture_slots.add() - # tex_slot.name = tex.name + 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) + 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: + # No more slots + 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] return {'FINISHED'} - class TextureSlotPanel(TextureButtonsPanel): """Use this class to show pov texture slots panel.""" @@ -2586,7 +2816,7 @@ class TEXTURE_PT_POV_type(TextureButtonsPanel, Panel): tex = context.texture split = layout.split(factor=0.2) - split.label(text="POV:") + split.label(text="Pattern") split.prop(tex.pov, "tex_pattern_type", text="") # row = layout.row() @@ -2631,6 +2861,7 @@ 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'} def draw(self, context): @@ -2911,6 +3142,112 @@ class TEXTURE_PT_POV_parameters(TextureButtonsPanel, Panel): row.prop(tex.pov, "warp_turbulence_z", text="Z") row.prop(tex.pov, "modifier_omega", text="Omega") +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' + + @classmethod + def poll(cls, context): + idblock = pov_context_tex_datablock(context) + if isinstance(idblock, Brush) and not context.sculpt_object: + return False + + if not getattr(context, "texture_slot", None): + return False + + engine = context.scene.render.engine + return (engine in cls.COMPAT_ENGINES) + + def draw(self, context): + layout = self.layout + + idblock = pov_context_tex_datablock(context) + + #tex = context.texture_slot + tex = mat.pov_texture_slots[ + mat.active_texture_index + ] + if not isinstance(idblock, Brush): + split = layout.split(percentage=0.3) + col = split.column() + col.label(text="Coordinates:") + col = split.column() + col.prop(tex, "texture_coords", text="") + + if tex.texture_coords == 'ORCO': + """ + ob = context.object + if ob and ob.type == 'MESH': + split = layout.split(percentage=0.3) + split.label(text="Mesh:") + split.prop(ob.data, "texco_mesh", text="") + """ + elif tex.texture_coords == 'UV': + split = layout.split(percentage=0.3) + split.label(text="Map:") + ob = context.object + 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': + split = layout.split(percentage=0.3) + split.label(text="Object:") + split.prop(tex, "object", text="") + + elif tex.texture_coords == 'ALONG_STROKE': + split = layout.split(percentage=0.3) + split.label(text="Use Tips:") + split.prop(tex, "use_tips", text="") + + if isinstance(idblock, Brush): + if context.sculpt_object or context.image_paint_object: + brush_texture_settings(layout, idblock, context.sculpt_object) + else: + if isinstance(idblock, FreestyleLineStyle): + split = layout.split(percentage=0.3) + split.label(text="Projection:") + split.prop(tex, "mapping", text="") + + split = layout.split(percentage=0.3) + split.separator() + row = split.row() + row.prop(tex, "mapping_x", text="") + row.prop(tex, "mapping_y", text="") + row.prop(tex, "mapping_z", text="") + + elif isinstance(idblock, Material): + split = layout.split(percentage=0.3) + split.label(text="Projection:") + split.prop(tex, "mapping", text="") + + split = layout.split() + + col = split.column() + if tex.texture_coords in {'ORCO', 'UV'}: + col.prop(tex, "use_from_dupli") + if (idblock.type == 'VOLUME' and tex.texture_coords == 'ORCO'): + col.prop(tex, "use_map_to_bounds") + elif tex.texture_coords == 'OBJECT': + col.prop(tex, "use_from_original") + if (idblock.type == 'VOLUME'): + col.prop(tex, "use_map_to_bounds") + else: + col.label() + + col = split.column() + row = col.row() + row.prop(tex, "mapping_x", text="") + row.prop(tex, "mapping_y", text="") + row.prop(tex, "mapping_z", text="") + + row = layout.row() + row.column().prop(tex, "offset") + row.column().prop(tex, "scale") class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel): """Use this class to define pov texture influence buttons.""" @@ -2919,18 +3256,20 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel): COMPAT_ENGINES = {'POVRAY_RENDER'} bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' - # bl_context = 'texture' + #bl_context = 'texture' @classmethod def poll(cls, context): idblock = pov_context_tex_datablock(context) if ( - isinstance(idblock, Brush) - and bpy.context.scene.texture_context == 'OTHER' + # isinstance(idblock, Brush) and # Brush used for everything since 2.8 + context.scene.texture_context == 'OTHER' ): # XXX replace by isinstance(idblock, bpy.types.Brush) and ... return False - # if not getattr(context, "pov_texture_slot", None): - # return False + # Specify below also for pov_world_texture_slots, lights etc. + # to display for various types of slots but only when any + if not getattr(idblock, "pov_texture_slots", None): + return False engine = context.scene.render.engine return engine in cls.COMPAT_ENGINES @@ -2940,14 +3279,13 @@ class TEXTURE_PT_POV_influence(TextureSlotPanel, Panel): layout = self.layout idblock = pov_context_tex_datablock(context) - # tex = context.pov_texture_slot - mat = bpy.context.active_object.active_material - texslot = mat.pov_texture_slots[ - mat.active_texture_index + #mat = bpy.context.active_object.active_material + texslot = idblock.pov_texture_slots[ + idblock.pov.active_texture_index ] # bpy.data.textures[mat.active_texture_index] tex = bpy.data.textures[ - mat.pov_texture_slots[mat.active_texture_index].texture + idblock.pov_texture_slots[idblock.pov.active_texture_index].texture ] def factor_but(layout, toggle, factor, name): @@ -3756,7 +4094,7 @@ class OBJECT_PT_povray_replacement_text(ObjectButtonsPanel, Panel): ############################################################################### -class VIEW_MT_POV_primitives_add(bpy.types.Menu): +class VIEW_MT_POV_primitives_add(Menu): """Define the primitives menu with presets""" bl_idname = "VIEW_MT_POV_primitives_add" @@ -3777,7 +4115,7 @@ class VIEW_MT_POV_primitives_add(bpy.types.Menu): layout.menu(VIEW_MT_POV_import.bl_idname, text="Import", icon="IMPORT") -class VIEW_MT_POV_Basic_Shapes(bpy.types.Menu): +class VIEW_MT_POV_Basic_Shapes(Menu): """Use this class to sort simple primitives menu entries.""" bl_idname = "POVRAY_MT_basic_shape_tools" @@ -3859,7 +4197,7 @@ class VIEW_MT_POV_Basic_Shapes(bpy.types.Menu): ) -class VIEW_MT_POV_import(bpy.types.Menu): +class VIEW_MT_POV_import(Menu): """Use this class for the import menu.""" bl_idname = "POVRAY_MT_import_tools" @@ -3910,7 +4248,7 @@ def menu_func_import(self, context): # return True -class NODE_MT_POV_map_create(bpy.types.Menu): +class NODE_MT_POV_map_create(Menu): """Create maps""" bl_idname = "POVRAY_MT_node_map_create" @@ -4056,7 +4394,7 @@ def validinsert(ext): return ext in {".txt", ".inc", ".pov"} -class TEXT_MT_POV_insert(bpy.types.Menu): +class TEXT_MT_POV_insert(Menu): """Use this class to create a menu launcher in text editor for the TEXT_OT_POV_insert operator .""" bl_label = "Insert" @@ -4116,10 +4454,10 @@ class TEXT_PT_POV_custom_code(TextButtonsPanel, Panel): 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_POSE') + box.operator("render.render", icon='OUTLINER_DATA_ARMATURE') if text.pov.custom_code in {'text'}: rtext = bpy.context.space_data.text - box.operator("text.run", icon='POSE_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') @@ -4133,7 +4471,7 @@ class TEXT_PT_POV_custom_code(TextButtonsPanel, Panel): # Text editor templates from header menu -class TEXT_MT_POV_templates(bpy.types.Menu): +class TEXT_MT_POV_templates(Menu): """Use this class to create a menu for the same pov templates scenes as other pov IDEs.""" bl_label = "POV" @@ -4154,12 +4492,111 @@ def menu_func_templates(self, context): # Do not depend on POV being active renderer here... self.layout.menu("TEXT_MT_POV_templates") +############################################################################### +# Freestyle +############################################################################### +#import addon_utils +#addon_utils.paths()[0] +#addon_utils.modules() +#mod.bl_info['name'] == 'Freestyle SVG Exporter': +bpy.utils.script_paths("addons") +#render_freestyle_svg = os.path.join(bpy.utils.script_paths("addons"), "render_freestyle_svg.py") + +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(['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) + try: + 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_order = 11 + print(subclass.bl_info) + except: + pass + + #del render_freestyle_svg.RENDER_PT_SVGExporterPanel.bl_parent_id + + +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'} + + @classmethod + def poll(cls, context): + with_freestyle = bpy.app.build_options.freestyle + engine = context.scene.render.engine + return(with_freestyle and engine == 'POVRAY_RENDER') + def draw_header(self, context): + + #scene = context.scene + rd = context.scene.render + layout = self.layout + + if rd.use_freestyle: + layout.prop( + rd, "use_freestyle", text="", icon='LINE_DATA' + ) + + else: + layout.prop( + rd, "use_freestyle", text="", icon='OUTLINER_OB_IMAGE' + ) + + def draw(self, context): + rd = context.scene.render + layout = self.layout + layout.active = rd.use_freestyle + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + flow = layout.grid_flow( + row_major=True, + columns=0, + even_columns=True, + even_rows=False, + align=True, + ) + + flow.prop(rd, "line_thickness_mode", expand=True) + + if rd.line_thickness_mode == 'ABSOLUTE': + flow.prop(rd, "line_thickness") + + # Warning if the Freestyle SVG Exporter addon is not enabled + if not check_render_freestyle_svg(): + # col = box.column() + layout.label( + text="Please enable Freestyle SVG Exporter addon", icon="INFO" + ) + # layout.separator() + layout.operator( + "preferences.addon_show", + text="Go to Render: Freestyle SVG Exporter addon", + icon="PREFERENCES", + ).module = "render_freestyle_svg" classes = ( WORLD_PT_POV_world, WORLD_MT_POV_presets, WORLD_OT_POV_add_preset, - WORLD_TEXTURE_SLOTS_UL_List, + WORLD_TEXTURE_SLOTS_UL_POV_layerlist, + #WORLD_TEXTURE_SLOTS_UL_List, WORLD_PT_POV_mist, # RenderButtonsPanel, # ModifierButtonsPanel, @@ -4188,6 +4625,7 @@ classes = ( RENDER_PT_POV_photons, RENDER_PT_POV_antialias, RENDER_PT_POV_radiosity, + RENDER_PT_POV_filter, POV_RADIOSITY_MT_presets, RENDER_OT_POV_radiosity_add_preset, RENDER_PT_POV_media, @@ -4232,13 +4670,14 @@ classes = ( TEXT_MT_POV_insert, TEXT_PT_POV_custom_code, TEXT_MT_POV_templates, - # TEXTURE_PT_context, - # TEXTURE_PT_POV_povray_texture_slots, - TEXTURE_UL_POV_texture_slots, + TEXTURE_PT_context, + #TEXTURE_PT_POV_povray_texture_slots, + #TEXTURE_UL_POV_texture_slots, MATERIAL_TEXTURE_SLOTS_UL_POV_layerlist, - MATERIAL_OT_POV_texture_slot_add, - MATERIAL_OT_POV_texture_slot_remove, + TEXTURE_OT_POV_texture_slot_add, + TEXTURE_OT_POV_texture_slot_remove, TEXTURE_PT_POV_influence, + TEXTURE_PT_POV_mapping, ) @@ -4257,11 +4696,18 @@ def register(): # was used for parametric objects but made the other addon unreachable on # unregister for other tools to use created a user action call instead # addon_utils.enable("add_mesh_extra_objects", default_set=False, persistent=True) - # bpy.types.TEXTURE_PT_context_texture.prepend(TEXTURE_PT_POV_type) + if not povCentricWorkspace in bpy.app.handlers.load_post: + print("Adding POV wentric workspace on load handlers list") + bpy.app.handlers.load_post.append(povCentricWorkspace) def unregister(): + if povCentricWorkspace in bpy.app.handlers.load_post: + print("Removing POV wentric workspace from load handlers list") + bpy.app.handlers.load_post.remove(povCentricWorkspace) + + # from bpy.utils import unregister_class # bpy.types.TEXTURE_PT_context_texture.remove(TEXTURE_PT_POV_type) @@ -4274,4 +4720,5 @@ def unregister(): bpy.types.VIEW3D_MT_add.remove(menu_func_add) for cls in reversed(classes): - unregister_class(cls) + if cls != TEXTURE_PT_context: + unregister_class(cls)