diff --git a/__init__.py b/__init__.py index dbe2171e..ce0cd99c 100644 --- a/__init__.py +++ b/__init__.py @@ -22,253 +22,30 @@ # # # ##### END MIT LICENSE BLOCK ##### -import bpy -import bgl -import blf -import time +import addon_utils -from .rfb_utils.prefs_utils import get_pref -from .rfb_utils import string_utils -from .rfb_utils import register_utils from .rfb_logger import rfb_log from .rfb_utils.envconfig_utils import envconfig -from .rman_constants import RFB_FLOAT3 bl_info = { "name": "RenderMan For Blender", "author": "Pixar", - "version": (24, 4, 0), + "version": (25, 0, 0), "blender": (2, 83, 0), - "location": "Info Header, render engine menu", - "description": "RenderMan 24 integration", - "warning": "", + "location": "Render Properties > Render Engine > RenderMan", + "description": "RenderMan 25 integration", + "doc_url": "https://rmanwiki.pixar.com/display/RFB", "category": "Render"} __RMAN_ADDON_LOADED__ = False -class PRManRender(bpy.types.RenderEngine): - bl_idname = 'PRMAN_RENDER' - bl_label = "RenderMan" - bl_use_preview = False # Turn off preview renders - bl_use_save_buffers = True - bl_use_shading_nodes = True # We support shading nodes - bl_use_shading_nodes_custom = False - bl_use_eevee_viewport = True # Use Eevee for look dev viewport mode - bl_use_postprocess = True +def load_node_arrange(): + ''' + Make sure that the node_arrange addon is enabled + ''' - def __init__(self): - from . import rman_render - self.rman_render = rman_render.RmanRender.get_rman_render() - self.export_failed = None - if self.is_preview and self.rman_render.rman_swatch_render_running: - # if a preview render is requested and a swatch render is - # already in progress, ignore this render request - return - if self.rman_render.rman_interactive_running: - # If IPR is already running, just return. - # We report an error in render() if this is a render attempt - return - - def __del__(self): - try: - from . import rman_render - except ModuleNotFoundError: - return - - rr = rman_render.RmanRender.get_rman_render() - try: - if self.is_preview: - # If this was a preview render, return - return - except: - pass - - if rr.rman_running: - if rr.rman_interactive_running: - rfb_log().debug("Stop interactive render.") - rr.rman_is_live_rendering = False - elif rr.is_regular_rendering(): - rfb_log().debug("Stop render.") - rr.stop_render(stop_draw_thread=False) - - def update(self, data, depsgraph): - pass - - def view_update(self, context, depsgraph): - ''' - For viewport renders. Blender calls view_update when starting viewport renders - and/or something changes in the scene. - ''' - - # check if we are already doing a regular render - if self.rman_render.is_regular_rendering(): - return - - if self.export_failed: - return - - # if interactive rendering has not started, start it - if not self.rman_render.rman_interactive_running and self.rman_render.sg_scene is None: - self.rman_render.bl_engine = self - if not self.rman_render.start_interactive_render(context, depsgraph): - self.export_failed = True - return - self.export_failed = False - - if self.rman_render.rman_interactive_running and not self.rman_render.rman_license_failed: - self.rman_render.update_scene(context, depsgraph) - - def view_draw(self, context, depsgraph): - ''' - For viewport renders. Blender calls view_draw whenever it redraws the 3D viewport. - This is where we check for camera moves and draw pxiels from our - Blender display driver. - ''' - if self.export_failed: - return - if self.rman_render.rman_interactive_running and not self.rman_render.rman_license_failed: - self.rman_render.update_view(context, depsgraph) - - self._draw_pixels(context, depsgraph) - - def _increment_version_tokens(self, external_render=False): - bl_scene = bpy.context.scene - vi = get_pref('rman_scene_version_increment', default='MANUALLY') - ti = get_pref('rman_scene_take_increment', default='MANUALLY') - - if (vi == 'RENDER' and not external_render) or (vi == 'BATCH_RENDER' and external_render): - bl_scene.renderman.version_token += 1 - string_utils.set_var('version', bl_scene.renderman.version_token) - - if (ti == 'RENDER' and not external_render) or (ti == 'BATCH_RENDER' and external_render): - bl_scene.renderman.take_token += 1 - string_utils.set_var('take', bl_scene.renderman.take_token) - - def update_render_passes(self, scene=None, renderlayer=None): - # this method allows us to add our AOVs as ports to the RenderLayer node - # in the compositor. - - from .rfb_utils import display_utils - if self.is_preview: - return - - if self.rman_render.rman_render_into != 'blender': - return - - self.rman_render.rman_scene.bl_scene = scene - dspy_dict = display_utils.get_dspy_dict(self.rman_render.rman_scene) - self.register_pass(scene, renderlayer, "Combined", 4, "RGBA", 'COLOR') - for i, dspy_nm in enumerate(dspy_dict['displays'].keys()): - if i == 0: - continue - dspy = dspy_dict['displays'][dspy_nm] - dspy_chan = dspy['params']['displayChannels'][0] - chan_info = dspy_dict['channels'][dspy_chan] - chan_type = chan_info['channelType']['value'] - - if chan_type == 'color': - self.register_pass(scene, renderlayer, dspy_nm, 3, "RGB", 'COLOR') - elif chan_type in ['vector', 'normal', 'point']: - self.register_pass(scene, renderlayer, dspy_nm, 3, "XYZ", 'VECTOR') - else: - self.register_pass(scene, renderlayer, dspy_nm, 1, "Z", 'VALUE') - - def render(self, depsgraph): - ''' - Main render entry point. Blender calls this when doing final renders or preview renders. - ''' - - bl_scene = depsgraph.scene_eval - rm = bl_scene.renderman - baking = (rm.hider_type in ['BAKE', 'BAKE_BRICKMAP_SELECTED']) - - if self.rman_render.rman_interactive_running: - # report an error if a render is trying to start while IPR is running - if self.is_preview and get_pref('rman_do_preview_renders', False): - #self.report({'ERROR'}, 'Cannot start a preview render when IPR is running') - rfb_log().debug('Cannot start a preview render when IPR is running') - pass - elif not self.is_preview: - self.report({'ERROR'}, 'Cannot start a render when IPR is running') - return - elif self.is_preview: - # double check we're not already viewport rendering - if self.rman_render.rman_interactive_running: - if get_pref('rman_do_preview_renders', False): - rfb_log().error("Cannot preview render while viewport rendering.") - return - if not get_pref('rman_do_preview_renders', False): - # user has turned off preview renders, just load the placeholder image - self.rman_render.bl_scene = depsgraph.scene_eval - #self.rman_render._load_placeholder_image() - return - if self.rman_render.rman_swatch_render_running: - return - self.rman_render.bl_engine = self - self.rman_render.start_swatch_render(depsgraph) - elif baking: - self.rman_render.bl_engine = self - if rm.enable_external_rendering: - self.rman_render.start_external_bake_render(depsgraph) - elif not self.rman_render.start_bake_render(depsgraph, for_background=bpy.app.background): - return - elif rm.enable_external_rendering: - self.rman_render.bl_engine = self - self.rman_render.start_external_render(depsgraph) - self._increment_version_tokens(external_render=True) - else: - for_background = bpy.app.background - self.rman_render.bl_engine = self - if not self.rman_render.start_render(depsgraph, for_background=for_background): - return - if not for_background: - self._increment_version_tokens(external_render=False) - - def _draw_pixels(self, context, depsgraph): - - scene = depsgraph.scene - w = context.region.width - h = context.region.height - - if self.rman_render.rman_license_failed: - pos_x = w / 2 - 100 - pos_y = 20 - blf.enable(0, blf.SHADOW) - blf.shadow_offset(0, 1, -1) - blf.shadow(0, 5, 0.0, 0.0, 0.0, 0.8) - blf.size(0, 32, 36) - blf.position(0, pos_x, pos_y, 0) - blf.color(0, 1.0, 0.0, 0.0, 1.0) - blf.draw(0, "%s" % (self.rman_render.rman_license_failed_message)) - blf.disable(0, blf.SHADOW) - return - - # Draw text area that RenderMan is running. - if get_pref('draw_ipr_text', False) and not self.rman_render.rman_is_viewport_rendering: - - pos_x = w / 2 - 100 - pos_y = 20 - blf.enable(0, blf.SHADOW) - blf.shadow_offset(0, 1, -1) - blf.shadow(0, 5, 0.0, 0.0, 0.0, 0.8) - blf.size(0, 32, 36) - blf.position(0, pos_x, pos_y, 0) - blf.color(0, 1.0, 0.0, 0.0, 1.0) - blf.draw(0, "%s" % ('RenderMan Interactive Mode Running')) - blf.disable(0, blf.SHADOW) - - if not self.rman_render.rman_is_viewport_rendering: - return - - # Bind shader that converts from scene linear to display space, - bgl.glEnable(bgl.GL_BLEND) - bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA) - self.bind_display_space_shader(scene) - - self.rman_render.draw_pixels(w, h) - - self.unbind_display_space_shader() - bgl.glDisable(bgl.GL_BLEND) + if addon_utils.check('node_arrange')[1] is False: + addon_utils.enable('node_arrange') def load_addon(): global __RMAN_ADDON_LOADED__ @@ -283,6 +60,7 @@ def load_addon(): from . import rman_handlers from . import rfb_translations from . import rman_stats + from . import rman_engine rman_config.register() rman_properties.pre_register() @@ -294,6 +72,7 @@ def load_addon(): rman_handlers.register() rfb_translations.register() rman_stats.register() + rman_engine.register() __RMAN_ADDON_LOADED__ = True @@ -301,16 +80,11 @@ def load_addon(): rfb_log().error( "Error loading addon. Correct RMANTREE setting in addon preferences.") -classes = [ - PRManRender, -] - -def register(): - register_utils.rman_register_classes(classes) - +def register(): from . import preferences preferences.register() load_addon() + load_node_arrange() def unregister(): global __RMAN_ADDON_LOADED__ @@ -327,6 +101,4 @@ def unregister(): rman_operators.unregister() rfb_translations.unregister() rman_stats.unregister() - - register_utils.rman_unregister_classes(classes) - + rman_engine.unregister() diff --git a/changelog.txt b/changelog.txt index 9e636ddc..605f26b8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,77 @@ +v25.0 April 17, 2023 + +New Features: +* You can now use the Qt version of the preset browser and texture manager, which +matches what is available in the other RenderMan DCC plugins. To use the Qt version, +go to the addon preferences and change UI Framework from Native to Qt +* Blender's "Persistent Data" option is now supported. This is mostly useful in batch rendering +where for subsequent frames only differences in the scene are calculated, rather than +re-generating the whole scene. Note, this option is off by default. +* For external/batch rendering using Blender Batch style, there is a new Frame Chunking option, +which specifies how many frames each blender process will render (previously, each +blender process would only do one frame). +* We now auto load the Blender's builtin Node Arrange addon. This allows for auto +arranging nodes in the shader graph. Two of the operators have been added to the RenderMan +right click context menu. + +Changes: +* Scene translation time has been improved. In some cases, as much as 27% speed improvement +has been seen. +* IPR to "it" is no longer a separate setting. To IPR to "it", right click in the Viewport, +and go to RenderMan->Render->IPR to it. +* Support for fluid meshes has been added. +* We now export two new user attributes: "user:blender_is_instance" and "user:blender_instance_uv". +If "user:blender_is_instance" is set to 1, this indicates that the current object is an instance. +* For subdivision meshes, you can now select a face map to act as holes for your mesh (note, +this does not work in XPU). +* Socket names for terminal nodes (ex: RenderMan Material, Bxdf etc) have been renamed. For example, +old name (Bxdf), new name (bxdf_in). This is to match the names used in our other DCC plugins. +* Editing attributes or options during IPR should be faster. +* For meshes, exporting of the tangent and bitangent vectors is now off by default. +* The default Subsurface Model for PxrSurface has changed from "Jensen Dipole" to +"Exponential Path Traced". If you would like to revert the default back to "Jensen Dipole", +you can create an override json file for PxrSurface. The json file would look something like this: + +{ + "name": "PxrSurface.args", + "params": [ + { + "name": "subsurfaceType", + "default": 0, + "always_write": true + } + ] +} +See Customizing Blender in the docs on how to install this override file. + +* The clapboard icon in the viewport will now first display an option to either render in +the viewport or to "it" +* For float ramps, you can now select the interpolation type to be used +* You can now change the zoom factor for the Enhance operator in the addon preferences +* We now draw the texture map for gobo and cookie light filters, if the draw textured lights option +is turned on +* The External Rendering panel has been renamed to Batch Rendering +* We now use "it" for the preview image task for our batch render jobs, instead of "sho". +* Volumes are now added to the "global volume aggregate" by default. If you want to turn off this +behavior, turn off the "Global Volume Aggregate" checkbox on volume object properties. + +Bug Fixes: +* Fixed issues with instancing using geometry nodes. +* Fixed issue where channels in the stylized output were in random order +* Fixed issue where the FOV was not correct when the camera sensor width was not default, +and viewport was in perspective mode +* Fixed issue where using the cookie or gobo light filters was slow in the viewport +* Fixed numerous issues with group nodes in the shader graph. +* Frame step setting should not be respected when batch rendering in RIB mode. +* Fixed issues with light linking when a light or an object are no longer in the scene. +* Fixed issue where normals and vectors were not editable in the shader graph. +* Fixed issue where the viewport render progress bar color was incorrect. +* "it" inspector was displaying the wrong min/max samples for IPR +* Various fixes for the package scene operator +* Fix the viewport drawing of gobo and cookie light filters +* Fixed a bug where UDIM textures from linked collections was not working + + v24.4 April 22, 2022 Changes: diff --git a/preferences.py b/preferences.py index 590100a5..fd3cd13c 100644 --- a/preferences.py +++ b/preferences.py @@ -42,7 +42,6 @@ 'rman_xpu_gpu_selection': -1, 'rman_xpu_device': 'CPU', 'rman_xpu_cpu_devices': [], - 'draw_ipr_text': True, 'draw_panel_icon': True, 'path_fallback_textures_path': os.path.join('', 'textures'), 'path_fallback_textures_path_always': False, @@ -91,12 +90,72 @@ 'rman_roz_grpcServer': True, 'rman_roz_webSocketServer': False, 'rman_roz_webSocketServer_Port': 0, - 'rman_roz_stats_print_level': '1' + 'rman_roz_stats_print_level': '1', + 'rman_enhance_zoom_factor': 5, + 'rman_parent_lightfilter': False, + 'rman_tractor_hostname': 'tractor-engine', + 'rman_tractor_port': 80, + 'rman_tractor_local_user': True, + 'rman_tractor_user': '', + 'rman_tractor_priority': 1.0, + 'rman_tractor_service': 'PixarRender', + 'rman_tractor_envkeys': '', + 'rman_tractor_after': '', + 'rman_tractor_crews': '', + 'rman_tractor_tier': '', + 'rman_tractor_projects': '', + 'rman_tractor_comment': '', + 'rman_tractor_metadata': '', + 'rman_tractor_whendone': '', + 'rman_tractor_whenerror': '', + 'rman_tractor_whenalways': '', + 'rman_tractor_dirmaps': [] } class RendermanPreferencePath(bpy.types.PropertyGroup): path: StringProperty(name="", subtype='DIR_PATH') +class PRMAN_OT_add_dirmap(bpy.types.Operator): + bl_idname = "renderman.add_dirmap" + bl_label = "Add Dirmap" + bl_description = "Add a new dirmap" + + def execute(self, context): + addon = context.preferences.addons[__package__] + prefs = addon.preferences + dirmap = prefs.rman_tractor_dirmaps.add() + + return {'FINISHED'} + +class PRMAN_OT_remove_dirmap(bpy.types.Operator): + bl_idname = "renderman.remove_dirmap" + bl_label = "Remove Dirmap" + bl_description = "Remove a dirmap" + + index: IntProperty( + default=0 + ) + + def execute(self, context): + addon = context.preferences.addons[__package__] + prefs = addon.preferences + if self.properties.index < len(prefs.rman_tractor_dirmaps): + prefs.rman_tractor_dirmaps.remove(self.properties.index) + + return {'FINISHED'} + +class RendermanDirMap(bpy.types.PropertyGroup): + from_path: StringProperty(name="From", description="") + to_path: StringProperty(name="To", description="") + zone: EnumProperty( + name="Zone", + description="The zone that this dirmap should apply to. UNC is for Windows; NFS is for linux and macOS.", + default="NFS", + items=[('NFS', 'NFS', ''), + ('UNC', 'UNC', '') + ] + ) + class RendermanDeviceDesc(bpy.types.PropertyGroup): name: StringProperty(name="", default="") id: IntProperty(default=-1) @@ -226,11 +285,6 @@ def reload_rmantree(self, context): update=lambda s,c: fix_path(s, 'path_rmantree') ) - draw_ipr_text: BoolProperty( - name="Draw IPR Text", - description="Draw notice on View3D when IPR is active", - default=True) - draw_panel_icon: BoolProperty( name="Draw Panel Icon", description="Draw an icon on RenderMan Panels", @@ -409,7 +463,7 @@ def update_rman_logging_level(self, context): rman_invert_light_linking: BoolProperty( name="Invert Light Linking", default=False, - description="Invert the behavior of light linking. Only objects linked to the light in the light linking editor will be illuminated. Changing this requires an IPR restart.", + description="Invert the behavior of light linking (only applies if UI framework is set to Native). Only objects linked to the light in the light linking editor will be illuminated. Changing this requires an IPR restart.", ) rman_show_cycles_convert: BoolProperty( @@ -475,6 +529,12 @@ def update_rman_logging_level(self, context): ] ) + rman_show_wip_qt: BoolProperty( + name="Show WIP UI", + default=False, + description="Show WIP Qt UI. Not all of our UI have been completely converted to Qt. Turn this option off to go back to the native version, even if UI Framework is set to Qt." + ) + # For the preset browser rpbConfigFile: StringProperty(default='') rpbUserLibraries: CollectionProperty(type=RendermanPreferencePath) @@ -529,7 +589,120 @@ def update_stats_config(self, context): ], description="How much live stats to print", update=update_stats_config - ) + ) + + rman_enhance_zoom_factor: IntProperty( + name="Enhance Zoom Factor", + description="How much to zoom in when using the Enhance operator", + default=5, + min=2, + max=10 + ) + + rman_parent_lightfilter: BoolProperty( + name="Parent Filter to Light", + default=False, + description="If on, and a light is selected, attaching a light filter will parent the light filter to the selected light." + ) + + # Tractor preferences + rman_tractor_hostname: StringProperty( + name="Hostname", + default="tractor-engine", + description="Hostname of the Tractor engine to use to submit batch render jobs" + ) + + rman_tractor_port: IntProperty( + name="Port", + default=80, + description="Port number that the Tractor engine is listening on" + ) + + rman_tractor_local_user: BoolProperty( + name="Use Local User", + default=True, + description="Use the current logged in user to submit the tractor job" + ) + + rman_tractor_user: StringProperty( + name="Username", + default="", + description="Username to use to submit the tractor job" + ) + + rman_tractor_priority: FloatProperty( + name="Priority", + default=1.0, + description="Priority of your job" + ) + + rman_tractor_service: StringProperty( + name="Service", + default="PixarRender", + description="Service keys for your job" + ) + + rman_tractor_envkeys: StringProperty( + name="Environment Keys", + default="", + description="Multiple keys can be specified and should be space separated.", + ) + + rman_tractor_after: StringProperty( + name="After", + default="", + description="Delay start of job processing until given time\nFormat: MONTH/DAY HOUR:MINUTES\nEx: 11/24 13:45" + ) + + rman_tractor_crews: StringProperty( + name="Crews", + default="", + description="List of crews. See 'Crews' in the Tractor documentation", + ) + + rman_tractor_tier: StringProperty( + name="Tier", + default="", + description="Dispatching tier that the job belongs to. See 'Scheduling Modes' in the Tractor documentation" + ) + + rman_tractor_projects: StringProperty( + name="Projects", + default="", + description="Dispatching tier that the job belongs to. See 'Limits Configuration' in the Tractor documentation" + ) + + rman_tractor_comment: StringProperty( + name="Comments", + default="", + description="Additional comment about the job." + ) + + rman_tractor_metadata: StringProperty( + name="Meta Data", + default="", + description= "Meta data to add to the job." + ) + + rman_tractor_whendone: StringProperty( + name='When Done Command', + default='', + description="Command to run when job completes withour error." + ) + + rman_tractor_whenerror: StringProperty( + name='When Error Command', + default='', + description="Command to run if there is an error executing the job." + ) + + rman_tractor_whenalways: StringProperty( + name='When Always Command', + default='', + description="Command to run regardless if job completes with or without errors." + ) + + rman_tractor_dirmaps: bpy.props.CollectionProperty(type=RendermanDirMap) def draw_xpu_devices(self, context, layout): if self.rman_xpu_device == 'CPU': @@ -591,7 +764,9 @@ def draw(self, context): col.prop(self, 'rman_invert_light_linking') col.prop(self, 'rman_solo_collapse_nodes') col.prop(self, 'rman_use_blend_dir_token') + col.prop(self, 'rman_parent_lightfilter') col.prop(self, 'rman_editor') + col.prop(self, 'rman_enhance_zoom_factor') # XPU Prefs if sys.platform != ("darwin") and envconfig_utils.envconfig().has_xpu_license: @@ -642,9 +817,10 @@ def draw(self, context): col.prop(self, 'rman_viewport_draw_progress') if self.rman_viewport_draw_progress: col.prop(self, 'rman_viewport_progress_color') - col.prop(self, 'draw_ipr_text') col.prop(self, 'draw_panel_icon') col.prop(self, 'rman_ui_framework') + if self.rman_ui_framework == 'QT': + col.prop(self, 'rman_show_wip_qt') # Logging row = layout.row() @@ -654,6 +830,44 @@ def draw(self, context): col.prop(self, 'rman_logging_level') col.prop(self, 'rman_logging_file') + # Batch Rendering + row = layout.row() + row.label(text='Batch Rendering', icon_value=rman_r_icon.icon_id) + row = layout.row() + col = row.column() + col.prop(self, 'rman_tractor_hostname') + col.prop(self, 'rman_tractor_port') + col.prop(self, 'rman_tractor_local_user') + if not self.rman_tractor_local_user: + col.prop(self, 'rman_tractor_user') + col.prop(self, 'rman_tractor_priority') + col.prop(self, 'rman_tractor_service') + col.prop(self, 'rman_tractor_envkeys') + col.prop(self, 'rman_tractor_after') + col.prop(self, 'rman_tractor_crews') + col.prop(self, 'rman_tractor_tier') + col.prop(self, 'rman_tractor_projects') + col.prop(self, 'rman_tractor_comment') + col.prop(self, 'rman_tractor_metadata') + col.prop(self, 'rman_tractor_whendone') + col.prop(self, 'rman_tractor_whenerror') + col.prop(self, 'rman_tractor_whenalways') + + row = layout.row() + row.label(text='Directory Maps') + row = layout.row() + col = row.column() + col.operator('renderman.add_dirmap', text='+') + for i, dirmap in enumerate(self.rman_tractor_dirmaps): + dirmap_row = col.row() + dirmap_row.use_property_split = False + dirmap_row.use_property_decorate = True + dirmap_row.prop(dirmap, 'from_path') + dirmap_row.prop(dirmap, 'to_path') + dirmap_row.prop(dirmap, 'zone') + op = dirmap_row.operator('renderman.remove_dirmap', text='X') + op.index = i + # Advanced row = layout.row() row.use_property_split = False @@ -705,6 +919,9 @@ def draw(self, context): classes = [ RendermanPreferencePath, RendermanDeviceDesc, + PRMAN_OT_add_dirmap, + PRMAN_OT_remove_dirmap, + RendermanDirMap, RendermanPreferences ] diff --git a/rfb_api/__init__.py b/rfb_api/__init__.py index ac8a5526..a12f4689 100644 --- a/rfb_api/__init__.py +++ b/rfb_api/__init__.py @@ -1,9 +1,285 @@ from .. import rman_config from .. import rman_bl_nodes +from ..rfb_utils import shadergraph_utils +from ..rfb_utils import object_utils +from ..rfb_logger import rfb_log import bpy -import json -import pprint import os +import json + +def create_light(node_type): + '''Create a RenderMan light + + Arguments: + node_type (str) - name of the RenderMan light caller wants to create (ex: PxrRectLight) + + Returns: + (bpy.types.Object, bpy.types.Node) - the light object and light shader node + + ''' + bpy.ops.object.rman_add_light(rman_light_name=node_type) + ob = bpy.context.object + shader = ob.data.renderman.get_light_node() + return (ob, shader) + +def create_lightfilter(node_type): + '''Create a RenderMan light filter + + Arguments: + node_type (str) - name of the RenderMan light filter caller wants to create (ex: PxrRectLight) + + Returns: + (bpy.types.Object, bpy.types.Node) - the lightfilter object and lightfilter shader node + + ''' + bpy.ops.object.rman_add_light_filter(rman_lightfilter_name=node_type, add_to_selected=False) + ob = bpy.context.object + shader = ob.data.renderman.get_light_node() + return (ob, shader) + +def attach_lightfilter(light, lightfilter): + '''Attach a lightfilter to a light + + Arguments: + light (bpy.types.Object) - light object + lightfilter (bpy.types.Object) - lightfilter object + + Returns: + (bpy.types.Object, bpy.types.Node) - the lightfilter object and lightfilter shader node + + ''' + rman_type = object_utils._detect_primitive_(light) + if rman_type == 'LIGHT': + light_filter_item = light.data.renderman.light_filters.add() + light_filter_item.linked_filter_ob = lightfilter + elif shadergraph_utils.is_mesh_light(light): + mat = light.active_material + if mat: + light_filter_item = mat.renderman_light.light_filters.add() + light_filter_item.linked_filter_ob = lightfilter + +def create_monkey(as_subdiv=False): + '''Create a Suzanne model + + + Arguments: + as_subdiv (bool) - whether to convert to a subdivision mesh + + Returns: + (bpy.types.Object) + + ''' + bpy.ops.mesh.primitive_monkey_add() + ob = bpy.context.object + if as_subdiv: + bpy.ops.mesh.rman_convert_subdiv() + return ob + +def create_openvdb_node(openvdb=None): + '''Import an OpenVDB file. + + Arguments: + openvdb (str) - full path to an OpeVDB file + + Returns: + (bpy.types.Object) + + ''' + if not os.path.exists(openvdb): + return None + + bpy.ops.object.rman_add_rman_geo('EXEC_DEFAULT', rman_default_name='OpenVDB', rman_prim_type='', bl_prim_type='VOLUME', filepath=openvdb, rman_convert_to_zup=True) + ob = bpy.context.object + return ob + +def create_volume_box(): + '''Create a volume box. + + Returns: + (bpy.types.Object) + + ''' + + bpy.ops.object.rman_add_rman_geo('EXEC_DEFAULT', rman_default_name='RiVolume', rman_prim_type='RI_VOLUME', bl_prim_type='') + ob = bpy.context.object + return ob + +def create_ribarchive_node(ribarchive=None): + '''Import a RIB archive file. + + Arguments: + ribarchive (str) - full path to an RIB archive file + + Returns: + (bpy.types.Object) + + ''' + if not os.path.exists(ribarchive): + return None + + bpy.ops.object.rman_add_rman_geo('EXEC_DEFAULT', rman_default_name='RIB_Archive', rman_prim_type='DELAYED_LOAD_ARCHIVE', bl_prim_type='', filepath=ribarchive, rman_convert_to_zup=False) + ob = bpy.context.object + return ob + +def create_alembic_node(alembic=None): + '''Import an alembic file (delayed). + + Arguments: + alembic (str) - full path to an Alembic file + + Returns: + (bpy.types.Object) + + ''' + if not os.path.exists(alembic): + return None + + bpy.ops.object.rman_add_rman_geo('EXEC_DEFAULT', rman_default_name='rman_AlembicArchive', rman_prim_type='ALEMBIC', bl_prim_type='', filepath=alembic, rman_convert_to_zup=True) + ob = bpy.context.object + return ob + +def create_bxdf(node_type): + '''Create a bxdf shader node and material + + + Arguments: + node_type (str) - name of the Bxdf you want to create (ex: PxrSurface) + + Returns: + (bpy.types.Material, bpy.types.Node) - material and bxdf node + + ''' + material = shadergraph_utils.create_bxdf(node_type) + nt = material.node_tree + output = shadergraph_utils.find_node_from_nodetree(nt, 'RendermanOutputNode') + bxdf = output.inputs[0].links[0].from_node + return (material, bxdf) + +def create_displacement(material): + '''Create a PxrDisplace node for the given material + + Arguments: + node_type (bpy.types.Material) - material + + Returns: + (bpy.types.Node) - the PxrDisplace node + + ''' + nt = material.node_tree + output = shadergraph_utils.find_node_from_nodetree(nt, 'RendermanOutputNode') + if output is None: + return None + if output.inputs['displace_in'].is_linked: + return output.inputs['displace_in'].links[0].from_node + disp = nt.nodes.new('PxrDisplaceDisplaceNode') + nt.links.new(disp.outputs['displace_out'], output.inputs['displace_in']) + return disp + +def attach_material(material, ob): + '''Attach material to object + + + Arguments: + node_type (bpy.types.Material) - material + ob (bpy.types.Object) - object to attach material to + + Returns: + (bool) - True if material attached. False otherwise + + ''' + try: + if ob.type == 'EMPTY': + ob.renderman.rman_material_override = material + else: + ob.active_material = material + except: + return False + return True + +def create_pattern(node_type, material): + '''Create a pattern node + + + Arguments: + node_type (str) - name of the pattern node you want to create (ex: PxrChecker) + material (bpy.types.Material) - material to create the pattern node in + + Returns: + (bpy.types.Node) - pattern node + + ''' + nt = material.node_tree + pattern = nt.nodes.new('%sPatternNode' % node_type) + return pattern + +def connect_nodes(output_node, output_socket, input_node, input_socket): + '''Connect shading nodes + + Example: + import RenderManForBlender.rfb_api as rfb_api + + # create material and bxdf + material, bxdf = rfb_api.create_bxdf('PxrDisneyBsdf') + + # create PxrChecker pattern node + checker = rfb_api.create_pattern('PxrChecker', material) + checker.colorB = (0.0, 1.0, 0.4) + + # connect PxrChecker to Bxdf + rfb_api.connect_nodes(checker, 'resultRGB', bxdf, 'baseColor') + + + Arguments: + output_node (bpy.types.Node) - output node + output_socket (str) - output node socket name + input_node (bpy.types.Node) - input node + input_socket (str) - input node socket name + + Returns: + (bool) - True if connection was successful. False otherwise. + + ''' + if output_node.id_data != input_node.id_data: + rfb_log().error("%s and %s are from different node trees" % (output_node.name, input_node.name)) + return False + if output_socket not in output_node.outputs: + rfb_log().error("%s does not have output %s" % (output_node.name, output_socket)) + return False + if input_socket not in input_node.inputs: + rfb_log().error("%s does not have input %s" % (input_node.name, input_socket)) + return False + + nt = output_node.id_data + nt.links.new(output_node.outputs[output_socket], input_node.inputs[input_socket]) + + return True + +def get_node_inputs(node): + ''' + Return a list of the node's inputs' names + + Arguments: + node (bpy.types.Node) - the shading node we want to interrogate + + Returns: + (list) - the list of names of the node's inputs + + ''' + return node.inputs.keys() + +def get_node_outputs(node): + ''' + Return a list of the node's outputs' names + + Arguments: + node (bpy.types.Node) - the shading node we want to interrogate + + Returns: + (list) - the list of names of the node's outputs + + ''' + return node.outputs.keys() + def GetConfigurablePanels(): '''Return the names of RenderForBlender panels that are configurable. diff --git a/rfb_icons/out_LamaIridescence.png b/rfb_icons/out_LamaIridescence.png new file mode 100644 index 00000000..c9465cae Binary files /dev/null and b/rfb_icons/out_LamaIridescence.png differ diff --git a/rfb_utils/display_utils.py b/rfb_utils/display_utils.py index ca01c0ae..741aa615 100644 --- a/rfb_utils/display_utils.py +++ b/rfb_utils/display_utils.py @@ -1,7 +1,5 @@ from . import string_utils from . import property_utils -from . import shadergraph_utils -from . import scene_utils from .. import rman_constants from .. import rman_config from collections import OrderedDict @@ -9,29 +7,45 @@ import bpy import os import getpass +import re __BLENDER_TO_RMAN_DSPY__ = { 'TIFF': 'tiff', 'TARGA': 'targa', 'TARGA_RAW': 'targa', 'OPEN_EXR': 'openexr', 'PNG': 'png'} +__RMAN_TO_BLENDER__ = { 'tiff': 'TIFF', 'targa': 'TARGA', 'openexr':'OPEN_EXR', 'png':'PNG'} -def get_channel_name(aov, layer_name): - aov_name = aov.name.replace(' ', '') - aov_channel_name = aov.channel_name - if not aov.aov_name or not aov.channel_name: - return '' - elif aov.aov_name == "color rgba": - aov_channel_name = "Ci,a" - # Remaps any color lpe channel names to a denoise friendly one - elif aov_name in channel_name_map.keys(): - aov_channel_name = '%s_%s_%s' % ( - channel_name_map[aov_name], aov_name, layer_name) - - elif aov.aov_name == "color custom_lpe": - aov_channel_name = aov.name +def get_beauty_filepath(bl_scene, use_blender_frame=False, expand_tokens=False, no_ext=False): + dspy_info = dict() + view_layer = bpy.context.view_layer + rm_rl = None + if view_layer.renderman.use_renderman: + rm_rl = view_layer.renderman + rm = bl_scene.renderman - else: - aov_channel_name = '%s_%s' % ( - aov_name, layer_name) + string_utils.set_var('scene', bl_scene.name.replace(' ', '_')) + string_utils.set_var('layer', view_layer.name.replace(' ', '_')) - return aov_channel_name + filePath = rm.path_beauty_image_output + if use_blender_frame: + filePath = re.sub(r'<[f|F]\d*>', '####', filePath) + if no_ext: + filePath = filePath.replace('', '') + if rm_rl: + aov = rm_rl.custom_aovs[0] + display_driver = aov.displaydriver + else: + file_format = bl_scene.render.image_settings.file_format + display_driver = __BLENDER_TO_RMAN_DSPY__.get(file_format, 'openexr') + + if expand_tokens: + filePath = string_utils.expand_string(filePath, + display=display_driver, + asFilePath=True) + dspy_info['filePath'] = filePath + dspy_info['display_driver'] = display_driver + return dspy_info + +def using_rman_displays(): + view_layer = bpy.context.view_layer + return view_layer.renderman.use_renderman def _default_dspy_params(): d = {} @@ -119,7 +133,6 @@ def _add_stylized_channels(dspys_dict, dspy_drv, rman_scene, expandTokens): if expandTokens: filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, asFilePath=True) dspys_dict['displays'][dspy_name] = { @@ -132,7 +145,7 @@ def _add_stylized_channels(dspys_dict, dspy_drv, rman_scene, expandTokens): 'params': dspy_params, 'dspyDriverParams': None} -def _add_denoiser_channels(dspys_dict, dspy_params): +def _add_denoiser_channels(dspys_dict, dspy_params, rman_scene): """ Add the necessary dspy channels for denoiser. We assume the beauty display will be used as the variance file @@ -156,9 +169,10 @@ def _add_denoiser_channels(dspys_dict, dspy_params): dspys_dict['displays']['beauty']['params']['displayChannels'].append(chan) - filePath = dspys_dict['displays']['beauty']['filePath'] - f,ext = os.path.splitext(filePath) - dspys_dict['displays']['beauty']['filePath'] = f + '_variance' + ext + if rman_scene.bl_scene.renderman.use_legacy_denoiser: + filePath = dspys_dict['displays']['beauty']['filePath'] + f,ext = os.path.splitext(filePath) + dspys_dict['displays']['beauty']['filePath'] = f + '_variance' + ext dspys_dict['displays']['beauty']['is_variance'] = True def _set_blender_dspy_dict(layer, dspys_dict, dspy_drv, rman_scene, expandTokens, do_optix_denoise=False): @@ -197,7 +211,6 @@ def _set_blender_dspy_dict(layer, dspys_dict, dspy_drv, rman_scene, expandTokens if expandTokens: filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, asFilePath=True) dspys_dict['displays']['beauty'] = { 'driverNode': display_driver, @@ -237,7 +250,6 @@ def _set_blender_dspy_dict(layer, dspys_dict, dspy_drv, rman_scene, expandTokens token_dict = {'aov': name} filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, token_dict=token_dict, asFilePath=True) if doit: @@ -326,8 +338,7 @@ def _add_chan_to_dpsychan_list(rm, rm_rl, dspys_dict, chan): d[u'remap_c'] = { 'type': u'float', 'value': chan.remap_c} d[u'exposure'] = { 'type': u'float2', 'value': [chan.exposure_gain, chan.exposure_gamma] } - if rm.hider_pixelFilterMode != 'importance': - # per channel filter does not work in importance mode + if chan.chan_pixelfilter != 'default': d[u'filter'] = {'type': u'string', 'value': chan.chan_pixelfilter} d[u'filterwidth'] = { 'type': u'float2', 'value': [chan.chan_pixelfilter_x, chan.chan_pixelfilter_y]} @@ -390,7 +401,6 @@ def _set_rman_dspy_dict(rm_rl, dspys_dict, dspy_drv, rman_scene, expandTokens, d token_dict = {'aov': aov.name} filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, token_dict=token_dict, asFilePath=True) @@ -407,7 +417,6 @@ def _set_rman_dspy_dict(rm_rl, dspys_dict, dspy_drv, rman_scene, expandTokens, d if expandTokens: filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, asFilePath=True) else: filePath = rm.path_aov_image_output @@ -415,7 +424,6 @@ def _set_rman_dspy_dict(rm_rl, dspys_dict, dspy_drv, rman_scene, expandTokens, d token_dict = {'aov': aov.name} filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, token_dict=token_dict, asFilePath=True) @@ -465,7 +473,7 @@ def _set_rman_dspy_dict(rm_rl, dspys_dict, dspy_drv, rman_scene, expandTokens, d 'dspyDriverParams': param_list } if aov_denoise and display_driver == 'openexr' and not rman_scene.is_interactive: - _add_denoiser_channels(dspys_dict, dspy_params) + _add_denoiser_channels(dspys_dict, dspy_params, rman_scene) if aov.name == 'beauty' and rman_scene.is_interactive: @@ -494,7 +502,7 @@ def _set_rman_dspy_dict(rm_rl, dspys_dict, dspy_drv, rman_scene, expandTokens, d 'params': dspy_params, 'dspyDriverParams': None} -def _set_rman_holdouts_dspy_dict(dspys_dict, dspy_drv, rman_scene, expandTokens): +def _set_rman_holdouts_dspy_dict(dspys_dict, dspy_drv, rman_scene, expandTokens, include_holdouts=True): rm = rman_scene.bl_scene.renderman display_driver = dspy_drv @@ -505,24 +513,28 @@ def _set_rman_holdouts_dspy_dict(dspys_dict, dspy_drv, rman_scene, expandTokens) if display_driver == 'openexr': param_list.SetInteger('asrgba', 1) - dspy_params = {} - dspy_params['displayChannels'] = [] - d = _default_dspy_params() - occluded_src = "color lpe:holdouts;C[DS]+" - d[u'channelSource'] = {'type': u'string', 'value': occluded_src} - d[u'channelType'] = { 'type': u'string', 'value': 'color'} - dspys_dict['channels']['occluded'] = d - dspy_params['displayChannels'].append('occluded') - - dspys_dict['displays']['occluded'] = { - 'driverNode': 'null', - 'filePath': 'occluded', - 'denoise': False, - 'denoise_mode': 'singleframe', - 'camera': None, - 'bake_mode': None, - 'params': dspy_params, - 'dspyDriverParams': None} + if include_holdouts: + dspy_params = {} + dspy_params['displayChannels'] = [] + d = _default_dspy_params() + occluded_src = "color lpe:holdouts;C[DS]+" + d[u'channelSource'] = {'type': u'string', 'value': occluded_src} + d[u'channelType'] = { 'type': u'string', 'value': 'color'} + dspys_dict['channels']['occluded'] = d + dspy_params['displayChannels'].append('occluded') + + dspys_dict['displays']['occluded'] = { + 'driverNode': 'null', + 'filePath': 'occluded', + 'denoise': False, + 'denoise_mode': 'singleframe', + 'camera': None, + 'bake_mode': None, + 'params': dspy_params, + 'dspyDriverParams': None} + + if rm.do_holdout_matte != "AOV" and not include_holdouts: + return dspy_params = {} dspy_params['displayChannels'] = [] @@ -541,7 +553,6 @@ def _set_rman_holdouts_dspy_dict(dspys_dict, dspy_drv, rman_scene, expandTokens) if expandTokens: filePath = string_utils.expand_string(filePath, display=display_driver, - frame=rman_scene.bl_frame_current, asFilePath=True) dspys_dict['displays']['holdoutMatte'] = { @@ -562,9 +573,26 @@ def _set_rman_holdouts_dspy_dict(dspys_dict, dspy_drv, rman_scene, expandTokens) 'camera': None, 'bake_mode': None, 'params': dspy_params, - 'dspyDriverParams': None} + 'dspyDriverParams': None} -def get_dspy_dict(rman_scene, expandTokens=True): +def any_dspys_denoise(view_layer): + rm_rl = None + if view_layer.renderman.use_renderman: + rm_rl = view_layer.renderman + if rm_rl: + for aov in rm_rl.custom_aovs: + if aov.denoise: + return True + return False + +def get_renderman_layer(view_layer=None): + if view_layer is None: + view_layer = bpy.context.view_layer + if view_layer.renderman.use_renderman: + return view_layer.renderman + return None + +def get_dspy_dict(rman_scene, expandTokens=True, include_holdouts=True): """ Create a dictionary of display channels and displays. The layout: @@ -621,7 +649,7 @@ def get_dspy_dict(rman_scene, expandTokens=True): do_optix_denoise = False if rman_scene.is_interactive: - display_driver = rm.render_ipr_into + display_driver = rman_scene.ipr_render_into do_optix_denoise = rm.blender_ipr_optix_denoiser elif (not rman_scene.external_render): @@ -642,12 +670,12 @@ def get_dspy_dict(rman_scene, expandTokens=True): _add_stylized_channels(dspys_dict, display_driver, rman_scene, expandTokens) if rm.do_holdout_matte != "OFF": - _set_rman_holdouts_dspy_dict(dspys_dict, display_driver, rman_scene, expandTokens) + _set_rman_holdouts_dspy_dict(dspys_dict, display_driver, rman_scene, expandTokens, include_holdouts=include_holdouts) return dspys_dict -def make_dspy_info(scene): +def make_dspy_info(scene, is_interactive=False): """ Create some render parameter from scene and pass it to image tool. @@ -657,11 +685,14 @@ def make_dspy_info(scene): Arguments: scene (bpy.types.Scene) - Blender scene object + is_interactive (bool) - True if we are in IPR Returns: (str) - a string with the display notes to give to "it" """ + from . import shadergraph_utils + params = {} rm = scene.renderman world = scene.world @@ -675,7 +706,10 @@ def make_dspy_info(scene): dspy_notes = "Render start:\t%s\r\r" % ts dspy_notes += "Integrator:\t%s\r\r" % integrator_nm - dspy_notes += "Samples:\t%d - %d\r" % (rm.hider_minSamples, rm.hider_maxSamples) + if is_interactive: + dspy_notes += "Samples:\t%d - %d\r" % (rm.ipr_hider_minSamples, rm.ipr_hider_maxSamples) + else: + dspy_notes += "Samples:\t%d - %d\r" % (rm.hider_minSamples, rm.hider_maxSamples) dspy_notes += "Pixel Variance:\t%f\r\r" % rm.ri_pixelVariance # moved this in front of integrator check. Was called redundant in @@ -709,11 +743,11 @@ def export_metadata(scene, params, camera_name=None): params (RtParamList) - param list to fill with meta data camera_name - Name of camera we want meta data from """ + from . import shadergraph_utils rm = scene.renderman world = scene.world output_dir = string_utils.expand_string(rm.path_rib_output, - frame=scene.frame_current, asFilePath=True) output_dir = os.path.dirname(output_dir) statspath=os.path.join(output_dir, 'stats.%04d.xml' % scene.frame_current) diff --git a/rfb_utils/draw_utils.py b/rfb_utils/draw_utils.py index 79bd095d..e662ad78 100644 --- a/rfb_utils/draw_utils.py +++ b/rfb_utils/draw_utils.py @@ -174,7 +174,7 @@ def _draw_ui_from_rman_config(config_name, panel, context, layout, parent): page_prop = '' page_open = False page_name = '' - editable = getattr(ndp, 'editable', False) + ipr_editable = getattr(ndp, 'ipr_editable', False) is_enabled = True if hasattr(ndp, 'page') and ndp.page != '': page_prop = ndp.page + "_uio" @@ -268,7 +268,7 @@ def _draw_ui_from_rman_config(config_name, panel, context, layout, parent): row.prop(parent, ndp.name, text=label) if is_rman_interactive_running: - row.enabled = editable + row.enabled = ipr_editable elif is_rman_running: row.enabled = False else: @@ -379,7 +379,7 @@ def draw_prop(node, prop_name, layout, level=0, nt=None, context=None, sticky=Fa ramp_node = node_group.nodes[ramp_name] layout.enabled = (nt.library is None) layout.template_color_ramp( - ramp_node, 'color_ramp') + ramp_node, 'color_ramp') return elif bl_prop_info.widget == 'floatramp': node_group = node.rman_fake_node_group_ptr @@ -392,7 +392,11 @@ def draw_prop(node, prop_name, layout, level=0, nt=None, context=None, sticky=Fa ramp_node = node_group.nodes[ramp_name] layout.enabled = (nt.library is None) layout.template_curve_mapping( - ramp_node, 'mapping') + ramp_node, 'mapping') + + interp_name = '%s_Interpolation' % prop_name + if hasattr(node, interp_name): + layout.prop(node, interp_name, text='Ramp Interpolation') return elif bl_prop_info.widget == 'displaymetadata': @@ -475,7 +479,7 @@ def panel_node_draw(layout, context, id_data, output_type, input_name): return True -def draw_nodes_properties_ui(layout, context, nt, input_name='Bxdf', +def draw_nodes_properties_ui(layout, context, nt, input_name='bxdf_in', output_node_type="output"): output_node = next((n for n in nt.nodes if hasattr(n, 'renderman_node_type') and n.renderman_node_type == output_node_type), None) @@ -489,9 +493,9 @@ def draw_nodes_properties_ui(layout, context, nt, input_name='Bxdf', layout.context_pointer_set("node", output_node) layout.context_pointer_set("socket", socket) - if input_name not in ['Light', 'LightFilter']: + if input_name not in ['light_in', 'lightfilter_in']: split = layout.split(factor=0.35) - split.label(text=socket.name + ':') + split.label(text=socket.identifier + ':') split.context_pointer_set("socket", socket) split.context_pointer_set("node", output_node) diff --git a/rfb_utils/envconfig_utils.py b/rfb_utils/envconfig_utils.py index 60c2ae5e..37ebdc60 100644 --- a/rfb_utils/envconfig_utils.py +++ b/rfb_utils/envconfig_utils.py @@ -75,7 +75,6 @@ def config_environment(self): self._set_it_path() self._set_localqueue_path() self._set_license_app_path() - self._config_pythonpath() self._set_ocio() self._get_license_info() @@ -139,25 +138,30 @@ def get_shader_registration_paths(self): return paths - def _config_pythonpath(self): + def config_pythonpath(self): python_vers = 'python%s' % rman_constants.BLENDER_PYTHON_VERSION rfb_log().debug("Blender Python Version: %s" % rman_constants.BLENDER_PYTHON_VERSION) if platform.system() == 'Windows': rman_packages = os.path.join(self.rmantree, 'lib', python_vers, 'Lib', 'site-packages') else: rman_packages = os.path.join(self.rmantree, 'lib', python_vers, 'site-packages') + if not os.path.exists(rman_packages): + return False + sys.path.append(rman_packages) sys.path.append(os.path.join(self.rmantree, 'bin')) pythonbindings = os.path.join(self.rmantree, 'bin', 'pythonbindings') - sys.path.append(pythonbindings) - + sys.path.append(pythonbindings) + if platform.system() == 'Windows': # apparently, we need to do this for windows app versions # of Blender, otherwise the rman python modules don't load os.add_dll_directory(rman_packages) os.add_dll_directory(os.path.join(self.rmantree, 'bin')) os.add_dll_directory(pythonbindings) - os.add_dll_directory(os.path.join(self.rmantree, 'lib')) + os.add_dll_directory(os.path.join(self.rmantree, 'lib')) + + return True def _append_to_path(self, path): if path is not None: @@ -393,13 +397,20 @@ def _guess_rmantree(): if buildinfo._version_major == rman_constants.RMAN_SUPPORTED_VERSION_MAJOR and buildinfo._version_minor < rman_constants.RMAN_SUPPORTED_VERSION_MINOR: rfb_log().error("Error loading addon using RMANTREE=%s. The minor version found (%s) is not supported. Minimum version supported is %s." % (rmantree, buildinfo._version_minor, rman_constants.RMAN_SUPPORTED_VERSION_STRING)) __RMAN_ENV_CONFIG__ = None - return None + return None rfb_log().debug("Guessed RMANTREE: %s" % rmantree) # Create an RmanEnvConfig object __RMAN_ENV_CONFIG__.rmantree = rmantree __RMAN_ENV_CONFIG__.build_info = buildinfo + + # configure python path + if not __RMAN_ENV_CONFIG__.config_pythonpath(): + rfb_log().error("The Python version this Blender uses (%s) is not supported by this version of RenderMan (%s)" % (rman_constants.BLENDER_PYTHON_VERSION, rman_constants.RMAN_SUPPORTED_VERSION_STRING)) + __RMAN_ENV_CONFIG__ = None + return None + __RMAN_ENV_CONFIG__.config_environment() return __RMAN_ENV_CONFIG__ diff --git a/rfb_utils/filepath_utils.py b/rfb_utils/filepath_utils.py index bb9e4f95..b6ccf0a9 100644 --- a/rfb_utils/filepath_utils.py +++ b/rfb_utils/filepath_utils.py @@ -6,6 +6,7 @@ import re from ..rfb_logger import rfb_log from .prefs_utils import get_pref +from . import string_utils def view_file(file_path): @@ -74,8 +75,14 @@ def get_token_blender_file_path(p): regex = r"^//" pout = re.sub(regex, '/', p, 0, re.MULTILINE) else: - pout = p - + blend_dir = string_utils.get_var('blend_dir') + if blend_dir == '': + pout = p + elif blend_dir.endswith('/'): + pout = p.replace(blend_dir, '') + else: + pout = p.replace(blend_dir, '/') + return pout.replace('\\', '/') def filesystem_path(p): diff --git a/rfb_utils/generate_property_utils.py b/rfb_utils/generate_property_utils.py index 20d34329..8339a580 100644 --- a/rfb_utils/generate_property_utils.py +++ b/rfb_utils/generate_property_utils.py @@ -46,6 +46,64 @@ def colorspace_names_list(): pass return items +def generate_string_enum(sp, param_label, param_default, param_help, set_function, get_function, update_function): + prop = None + if 'ocio_colorspaces' in sp.options: + def colorspace_names_options(self, context): + items = [] + items.append(('Disabled', 'Disabled', '')) + items.extend(colorspace_names_list()) + return items + + prop = EnumProperty(name=param_label, + description=param_help, + items=colorspace_names_options, + set=set_function, + get=get_function, + update=update_function) + else: + items = [] + + if param_default == '' or param_default == "''": + param_default = __RMAN_EMPTY_STRING__ + + in_items = False + + if isinstance(sp.options, list): + for v in sp.options: + if v == '' or v == "''": + v = __RMAN_EMPTY_STRING__ + items.append((str(v), str(v), '')) + if param_default == str(v): + in_items = True + else: + for k,v in sp.options.items(): + if v == '' or v == "''": + v = __RMAN_EMPTY_STRING__ + items.append((str(v), str(k), '')) + if param_default == str(v): + in_items = True + + if in_items: + prop = EnumProperty(name=param_label, + default=param_default, description=param_help, + items=items, + set=set_function, + get=get_function, + update=update_function) + else: + # for strings, assume the first item is the default + k = items[0][1] + items[0] = (param_default, k, '' ) + prop = EnumProperty(name=param_label, + default=param_default, description=param_help, + items=items, + set=set_function, + get=get_function, + update=update_function) + + return prop + def generate_colorspace_menu(node, param_name): '''Generate a colorspace enum property for the incoming parameter name @@ -139,7 +197,7 @@ def is_array(ndp): setattr(node, param_name, sub_prop_names) return True -def generate_property(node, sp, update_function=None): +def generate_property(node, sp, update_function=None, set_function=None, get_function=None): options = {'ANIMATABLE'} param_name = sp._name renderman_name = param_name @@ -172,6 +230,7 @@ def generate_property(node, sp, update_function=None): prop_stepsize = 3 if hasattr(sp, 'sensitivity'): prop_stepsize = -int(math.log10(sp.sensitivity)) + prop_precision = getattr(sp, 'digits', 3) prop = None @@ -219,6 +278,11 @@ def generate_property(node, sp, update_function=None): exec('update_func = %s' % update_function, globals(), lcls) update_function = lcls['update_func'] + if isinstance(set_function, str): + lcls = locals() + exec('set_func = %s' % set_function, globals(), lcls) + set_function = lcls['set_func'] + if param_widget == 'colorramp': from ..rman_properties.rman_properties_misc import RendermanBlColorRamp @@ -248,16 +312,18 @@ def generate_property(node, sp, update_function=None): elif param_type == 'float': if sp.is_array(): prop = FloatProperty(name=param_label, - default=0.0, precision=3, + default=0.0, precision=prop_precision, step=prop_stepsize, description=param_help, + set=set_function, + get=get_function, update=update_function) else: if param_widget in ['checkbox', 'switch']: prop = BoolProperty(name=param_label, default=bool(param_default), - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) elif param_widget == 'mapper': items = [] in_items = False @@ -282,7 +348,7 @@ def generate_property(node, sp, update_function=None): prop = EnumProperty(name=param_label, items=items, default=bl_default, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: param_min = sp.min if hasattr(sp, 'min') else (-1.0 * sys.float_info.max) param_max = sp.max if hasattr(sp, 'max') else sys.float_info.max @@ -290,10 +356,10 @@ def generate_property(node, sp, update_function=None): param_max = sp.slidermax if hasattr(sp, 'slidermax') else param_max prop = FloatProperty(name=param_label, - default=param_default, precision=3, + default=param_default, precision=prop_precision, soft_min=param_min, soft_max=param_max, step=prop_stepsize, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: param_min = sp.min if hasattr(sp, 'min') else (-1.0 * sys.float_info.max) @@ -302,10 +368,10 @@ def generate_property(node, sp, update_function=None): param_max = sp.slidermax if hasattr(sp, 'slidermax') else param_max prop = FloatProperty(name=param_label, - default=param_default, precision=3, + default=param_default, precision=prop_precision, soft_min=param_min, soft_max=param_max, step=prop_stepsize, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = 'float' @@ -314,17 +380,14 @@ def generate_property(node, sp, update_function=None): if sp.is_array(): prop = IntProperty(name=param_label, default=0, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: param_default = int(param_default) if param_default else 0 - # make invertT default 0 - if param_name == 'invertT': - param_default = 0 if param_widget in ['checkbox', 'switch']: prop = BoolProperty(name=param_label, default=bool(param_default), - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) elif param_widget == 'displaymetadata': from ..rman_bl_nodes.rman_bl_nodes_props import RendermanDspyMetaGroup @@ -365,7 +428,7 @@ def generate_property(node, sp, update_function=None): prop = EnumProperty(name=param_label, items=items, default=bl_default, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: param_min = int(sp.min) if hasattr(sp, 'min') else 0 param_max = int(sp.max) if hasattr(sp, 'max') else 2 ** 31 - 1 @@ -374,7 +437,7 @@ def generate_property(node, sp, update_function=None): default=param_default, soft_min=param_min, soft_max=param_max, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: param_min = int(sp.min) if hasattr(sp, 'min') else 0 @@ -384,7 +447,7 @@ def generate_property(node, sp, update_function=None): default=param_default, soft_min=param_min, soft_max=param_max, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = 'int' elif param_type == 'color': @@ -393,7 +456,7 @@ def generate_property(node, sp, update_function=None): default=(1.0, 1.0, 1.0), size=3, subtype="COLOR", soft_min=0.0, soft_max=1.0, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: if param_default == 'null' or param_default is None: param_default = (0.0,0.0,0.0) @@ -401,13 +464,13 @@ def generate_property(node, sp, update_function=None): default=param_default, size=3, subtype="COLOR", soft_min=0.0, soft_max=1.0, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = 'color' elif param_type == 'shader': param_default = '' prop = StringProperty(name=param_label, default=param_default, - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = 'string' elif param_type in ['string', 'struct', 'vstruct', 'bxdf']: if param_default is None: @@ -424,7 +487,7 @@ def generate_property(node, sp, update_function=None): if is_ies: prop = StringProperty(name=param_label, default=param_default, subtype="FILE_PATH", - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) else: prop = StringProperty(name=param_label, default=param_default, subtype="FILE_PATH", @@ -442,64 +505,19 @@ def generate_property(node, sp, update_function=None): description=param_help) elif param_widget in ['mapper', 'popup']: - if 'ocio_colorspaces' in sp.options: - def colorspace_names_options(self, context): - items = [] - items.append(('Disabled', 'Disabled', '')) - items.extend(colorspace_names_list()) - return items - - prop = EnumProperty(name=param_label, - description=param_help, - items=colorspace_names_options, - update=update_function) - else: - items = [] - - if param_default == '' or param_default == "''": - param_default = __RMAN_EMPTY_STRING__ - - in_items = False - - if isinstance(sp.options, list): - for v in sp.options: - if v == '' or v == "''": - v = __RMAN_EMPTY_STRING__ - items.append((str(v), str(v), '')) - if param_default == str(v): - in_items = True - else: - for k,v in sp.options.items(): - if v == '' or v == "''": - v = __RMAN_EMPTY_STRING__ - items.append((str(v), str(k), '')) - if param_default == str(v): - in_items = True - - if in_items: - prop = EnumProperty(name=param_label, - default=param_default, description=param_help, - items=items, - update=update_function) - else: - # for strings, assume the first item is the default - k = items[0][1] - items[0] = (param_default, k, '' ) - prop = EnumProperty(name=param_label, - default=param_default, description=param_help, - items=items, - update=update_function) + prop = generate_string_enum(sp, param_label, param_default, param_help, set_function, get_function, update_function) elif param_widget == 'bl_scenegraphlocation': reference_type = eval(sp.options['nodeType']) prop = PointerProperty(name=param_label, description=param_help, - type=reference_type) - + type=reference_type) + elif param_widget == 'null' and hasattr(sp, 'options'): + prop = generate_string_enum(sp, param_label, param_default, param_help, set_function, get_function, update_function) else: prop = StringProperty(name=param_label, default=str(param_default), - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = param_type elif param_type in ['vector', 'normal']: @@ -508,15 +526,17 @@ def colorspace_names_options(self, context): prop = FloatVectorProperty(name=param_label, default=param_default, size=3, subtype="NONE", - description=param_help, update=update_function) + precision=prop_precision, + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = param_type elif param_type == 'point': if param_default is None: param_default = '0 0 0' prop = FloatVectorProperty(name=param_label, default=param_default, size=3, + precision=prop_precision, subtype="XYZ", - description=param_help, update=update_function) + description=param_help, set=set_function, get=get_function, update=update_function) renderman_type = param_type elif param_type == 'int2': param_type = 'int' @@ -551,11 +571,11 @@ def colorspace_names_options(self, context): prop = EnumProperty(name=param_label, items=items, default=bl_default, - description=param_help, update=update_function) + description=param_help, set=set_function, update=update_function) else: prop = IntVectorProperty(name=param_label, default=param_default, size=2, - description=param_help, update=update_function) + description=param_help, set=set_function, update=update_function) renderman_type = 'int' prop_meta['arraySize'] = 2 @@ -592,12 +612,13 @@ def colorspace_names_options(self, context): prop = EnumProperty(name=param_label, items=items, default=bl_default, - description=param_help, update=update_function) + description=param_help, set=set_function, update=update_function) else: prop = FloatVectorProperty(name=param_label, default=param_default, size=2, step=prop_stepsize, - description=param_help, update=update_function) + precision=prop_precision, + description=param_help, set=set_function, update=update_function) renderman_type = 'float' prop_meta['arraySize'] = 2 diff --git a/rfb_utils/mesh_utils.py b/rfb_utils/mesh_utils.py new file mode 100644 index 00000000..b1ba1d03 --- /dev/null +++ b/rfb_utils/mesh_utils.py @@ -0,0 +1,61 @@ +import numpy as np + +def get_mesh_points_(mesh): + ''' + Get just the points for the input mesh. + + Arguments: + mesh (bpy.types.Mesh) - Blender mesh + + Returns: + (list) - the points on the mesh + ''' + + nvertices = len(mesh.vertices) + P = np.zeros(nvertices*3, dtype=np.float32) + mesh.vertices.foreach_get('co', P) + P = np.reshape(P, (nvertices, 3)) + return P.tolist() + +def get_mesh(mesh, get_normals=False): + ''' + Get the basic primvars needed to render a mesh. + + Arguments: + mesh (bpy.types.Mesh) - Blender mesh + get_normals (bool) - Whether or not normals are needed + + Returns: + (list) - this includes nverts (the number of vertices for each face), + vertices list, points, and normals + ''' + + P = get_mesh_points_(mesh) + N = [] + + npolygons = len(mesh.polygons) + fastnvertices = np.zeros(npolygons, dtype=np.int) + mesh.polygons.foreach_get('loop_total', fastnvertices) + nverts = fastnvertices.tolist() + + loops = len(mesh.loops) + fastvertices = np.zeros(loops, dtype=np.int) + mesh.loops.foreach_get('vertex_index', fastvertices) + verts = fastvertices.tolist() + + if get_normals: + fastsmooth = np.zeros(npolygons, dtype=np.int) + mesh.polygons.foreach_get('use_smooth', fastsmooth) + if mesh.use_auto_smooth or True in fastsmooth: + mesh.calc_normals_split() + fastnormals = np.zeros(loops*3, dtype=np.float32) + mesh.loops.foreach_get('normal', fastnormals) + fastnormals = np.reshape(fastnormals, (loops, 3)) + N = fastnormals.tolist() + else: + fastnormals = np.zeros(npolygons*3, dtype=np.float32) + mesh.polygons.foreach_get('normal', fastnormals) + fastnormals = np.reshape(fastnormals, (npolygons, 3)) + N = fastnormals.tolist() + + return (nverts, verts, P, N) \ No newline at end of file diff --git a/rfb_utils/object_utils.py b/rfb_utils/object_utils.py index f7af94e0..cbcd7120 100644 --- a/rfb_utils/object_utils.py +++ b/rfb_utils/object_utils.py @@ -1,8 +1,10 @@ import bpy -import numpy as np from .prefs_utils import get_pref from . import string_utils +# These types don't create instances +_RMAN_NO_INSTANCES_ = ['EMPTY', 'EMPTY_INSTANCER', 'LIGHTFILTER'] + def get_db_name(ob, rman_type='', psys=None): db_name = '' @@ -42,10 +44,11 @@ def get_group_db_name(ob_inst): ob = ob_inst.instance_object parent = ob_inst.parent psys = ob_inst.particle_system + persistent_id = "%d%d" % (ob_inst.persistent_id[1], ob_inst.persistent_id[0]) if psys: - group_db_name = "%s|%s|%s|%d|%d" % (parent.name_full, ob.name_full, psys.name, ob_inst.persistent_id[1], ob_inst.persistent_id[0]) + group_db_name = "%s|%s|%s|%s" % (parent.name_full, ob.name_full, psys.name, persistent_id) else: - group_db_name = "%s|%s|%d|%d" % (parent.name_full, ob.name_full, ob_inst.persistent_id[1], ob_inst.persistent_id[0]) + group_db_name = "%s|%s|%s" % (parent.name_full, ob.name_full, persistent_id) else: ob = ob_inst.object group_db_name = "%s" % (ob.name_full) @@ -54,12 +57,23 @@ def get_group_db_name(ob_inst): return string_utils.sanitize_node_name(group_db_name) +def is_light_filter(ob): + if ob is None: + return False + if ob.type != 'LIGHT': + return False + rm = ob.data.renderman + return (rm.renderman_light_role == 'RMAN_LIGHTFILTER') + def is_portal_light(ob): if ob.type != 'LIGHT': return False rm = ob.data.renderman return (rm.renderman_light_role == 'RMAN_LIGHT' and rm.get_light_node_name() == 'PxrPortalLight') +def is_empty_instancer(ob): + return (_detect_primitive_(ob) == 'EMPTY_INSTANCER') + def is_particle_instancer(psys, particle_settings=None): psys_settings = particle_settings if not psys_settings: @@ -140,7 +154,51 @@ def is_transforming(ob, recurse=False): transforming = ob.parent.data.use_path return transforming +def has_empty_parent(ob): + # check if the parent of ob is an Empty + if not ob: + return False + if not ob.parent: + return False + if _detect_primitive_(ob.parent) == 'EMPTY': + return True + return False + +def prototype_key(ob): + if isinstance(ob, bpy.types.DepsgraphObjectInstance): + if ob.is_instance: + if ob.object.data: + return '%s-DATA' % ob.object.data.name_full + else: + return '%s-OBJECT' % ob.object.name_full + if ob.object.data: + return '%s-DATA' % ob.object.data.name_full + return '%s-OBJECT' % ob.object.original.name_full + elif ob.data: + return '%s-DATA' % ob.original.data.original.name_full + return '%s-OBJECT' % ob.original.name_full + +def curve_is_mesh(ob): + ''' + Check if we need to consider this curve a mesh + ''' + is_mesh = False + if len(ob.modifiers) > 0: + is_mesh = True + elif len(ob.data.splines) < 1: + is_mesh = True + elif ob.data.dimensions == '2D' and ob.data.fill_mode != 'NONE': + is_mesh = True + else: + l = ob.data.extrude + ob.data.bevel_depth + if l > 0: + is_mesh = True + + return is_mesh + def _detect_primitive_(ob): + if ob is None: + return '' if isinstance(ob, bpy.types.ParticleSystem): return ob.settings.type @@ -162,6 +220,8 @@ def _detect_primitive_(ob): elif ob.type == 'FONT': return 'MESH' elif ob.type in ['CURVE']: + if curve_is_mesh(ob): + return 'MESH' return 'CURVE' elif ob.type == 'SURFACE': if get_pref('rman_render_nurbs_as_mesh', True): @@ -172,6 +232,8 @@ def _detect_primitive_(ob): elif ob.type == 'CAMERA': return 'CAMERA' elif ob.type == 'EMPTY': + if ob.is_instancer: + return 'EMPTY_INSTANCER' return 'EMPTY' elif ob.type == 'GPENCIL': return 'GPENCIL' @@ -215,43 +277,4 @@ def _get_used_materials_(ob): break return [mesh.materials[i] for i in mat_ids] else: - return [ob.active_material] - -def _get_mesh_points_(mesh): - nvertices = len(mesh.vertices) - P = np.zeros(nvertices*3, dtype=np.float32) - mesh.vertices.foreach_get('co', P) - P = np.reshape(P, (nvertices, 3)) - return P.tolist() - -def _get_mesh_(mesh, get_normals=False): - - P = _get_mesh_points_(mesh) - N = [] - - npolygons = len(mesh.polygons) - fastnvertices = np.zeros(npolygons, dtype=np.int) - mesh.polygons.foreach_get('loop_total', fastnvertices) - nverts = fastnvertices.tolist() - - loops = len(mesh.loops) - fastvertices = np.zeros(loops, dtype=np.int) - mesh.loops.foreach_get('vertex_index', fastvertices) - verts = fastvertices.tolist() - - if get_normals: - fastsmooth = np.zeros(npolygons, dtype=np.int) - mesh.polygons.foreach_get('use_smooth', fastsmooth) - if mesh.use_auto_smooth or True in fastsmooth: - mesh.calc_normals_split() - fastnormals = np.zeros(loops*3, dtype=np.float32) - mesh.loops.foreach_get('normal', fastnormals) - fastnormals = np.reshape(fastnormals, (loops, 3)) - N = fastnormals.tolist() - else: - fastnormals = np.zeros(npolygons*3, dtype=np.float32) - mesh.polygons.foreach_get('normal', fastnormals) - fastnormals = np.reshape(fastnormals, (npolygons, 3)) - N = fastnormals.tolist() - - return (nverts, verts, P, N) \ No newline at end of file + return [ob.active_material] \ No newline at end of file diff --git a/rfb_utils/prefs_utils.py b/rfb_utils/prefs_utils.py index dad83def..5f698504 100644 --- a/rfb_utils/prefs_utils.py +++ b/rfb_utils/prefs_utils.py @@ -12,6 +12,16 @@ def get_addon_prefs(): return v return None +def using_qt(): + if bpy.app.background: + return False + return get_pref('rman_ui_framework') == 'QT' + +def show_wip_qt(): + if bpy.app.background: + return False + return get_pref('rman_show_wip_qt') + def get_pref(pref_name='', default=None): """ Return the value of a preference diff --git a/rfb_utils/property_callbacks.py b/rfb_utils/property_callbacks.py index 238e9175..62918510 100644 --- a/rfb_utils/property_callbacks.py +++ b/rfb_utils/property_callbacks.py @@ -209,8 +209,24 @@ def update_integrator_func(self, context): update_conditional_visops(node) scenegraph_utils.update_sg_integrator(context) -def update_options_func(self, context): - scenegraph_utils.update_sg_options(context) +def update_options_func(self, s, context): + scenegraph_utils.update_sg_options(s, context) -def update_root_node_func(self, context): - scenegraph_utils.update_sg_root_node(context) \ No newline at end of file +def update_root_node_func(self, s, context): + scenegraph_utils.update_sg_root_node(s, context) + +def update_riattr_func(self, s, context): + ob = None + if not hasattr(context, 'object'): + ob = self.id_data + if ob is None: + return + scenegraph_utils.update_sg_node_riattr(s, context, bl_object=ob) + +def update_primvar_func(self, s, context): + ob = None + if not hasattr(context, 'object'): + ob = self.id_data + if ob is None: + return + scenegraph_utils.update_sg_node_primvar(s, context, bl_object=ob) \ No newline at end of file diff --git a/rfb_utils/property_utils.py b/rfb_utils/property_utils.py index 810f9e8b..530a1f8d 100644 --- a/rfb_utils/property_utils.py +++ b/rfb_utils/property_utils.py @@ -1,15 +1,9 @@ -from . import texture_utils from . import string_utils -from . import shadergraph_utils from . import prefs_utils -from ..rman_constants import RFB_ARRAYS_MAX_LEN, __RMAN_EMPTY_STRING__, __RESERVED_BLENDER_NAMES__, RFB_FLOAT3 +from ..rman_constants import __RMAN_EMPTY_STRING__, __RESERVED_BLENDER_NAMES__, RFB_FLOAT3 from ..rfb_logger import rfb_log -from collections import OrderedDict from bpy.props import * import bpy -import os -import shutil -import re __GAINS_TO_ENABLE__ = { @@ -54,6 +48,9 @@ class BlPropInfo: def __init__(self, node, prop_name, prop_meta): + + from . import shadergraph_utils + self.prop_meta = prop_meta self.prop_name = prop_name self.prop = getattr(node, prop_name, None) @@ -132,7 +129,7 @@ def get_property_default(node, prop_name): return dflt -def set_rix_param(params, param_type, param_name, val, is_reference=False, is_array=False, array_len=-1, node=None): +def set_rix_param(params, param_type, param_name, val, is_reference=False, is_array=False, array_len=-1, node=None, prop_name=''): """Sets a single parameter in an RtParamList Arguments: @@ -145,6 +142,7 @@ def set_rix_param(params, param_type, param_name, val, is_reference=False, is_ar array_len (int) - length of array node (AnyType) - the Blender object that this param originally came from. This is necessary so we can grab and compare val with the default value (see get_property_default) + prop_name (str) - name of the property that we look for the default value """ @@ -176,7 +174,11 @@ def set_rix_param(params, param_type, param_name, val, is_reference=False, is_ar else: # check if we need to emit this parameter. if node != None and not prefs_utils.get_pref('rman_emit_default_params', False): - dflt = get_property_default(node, param_name) + pname = param_name + if prop_name != '': + pname = prop_name + dflt = get_property_default(node, pname) + # FIXME/TODO: currently, the python version of RtParamList # doesn't allow us to retrieve existing values. For now, only do the @@ -184,35 +186,18 @@ def set_rix_param(params, param_type, param_name, val, is_reference=False, is_ar # not setting the value during IPR, if the user happens to change # the param val back to default. if dflt != None and not params.HasParam(param_name): - if isinstance(val, list): - dflt = list(dflt) - - if not is_array: - if ((isinstance(val, str) or isinstance(dflt, str))): - # these explicit conversions are necessary because of EnumProperties - if param_type == 'string' and val == __RMAN_EMPTY_STRING__: - val = "" - elif param_type == 'int': - val = int(val) - dflt = int(dflt) - elif param_type == 'float': - val = float(val) - dflt = float(dflt) - elif param_type == 'int' and (isinstance(val, bool) or isinstance(dflt, bool)): - # convert bools into ints - val = int(val) - dflt = int(dflt) + dflt = string_utils.convert_val(dflt, type_hint=param_type) # Check if this param is marked always_write. # We still have some plugins where the Args file and C++ don't agree # on default behavior always_write = False prop_meta = getattr(node, 'prop_meta', dict()) - if param_name in node.prop_meta: - meta = prop_meta.get(param_name) + if pname in node.prop_meta: + meta = prop_meta.get(pname) always_write = meta.get('always_write', always_write) if always_write: - rfb_log().debug('Param: %s for Node: %s is marked always_write' % (param_name, node.name)) + rfb_log().debug('Param: %s for Node: %s is marked always_write' % (pname, node.name)) if not always_write and val == dflt: return @@ -242,9 +227,100 @@ def set_rix_param(params, param_type, param_name, val, is_reference=False, is_ar elif param_type == "vector": params.SetVector(param_name, val) elif param_type == "normal": - params.SetNormal(param_name, val) + params.SetNormal(param_name, val) + +def set_primvar_bl_props(primvars, rm, inherit_node=None): + # set any properties marked primvar in the config file + for prop_name, meta in rm.prop_meta.items(): + set_primvar_bl_prop(primvars, prop_name, meta, rm, inherit_node=inherit_node) + +def set_primvar_bl_prop(primvars, prop_name, meta, rm, inherit_node): + if 'primvar' not in meta: + return + + conditionalVisOps = meta.get('conditionalVisOps', None) + if conditionalVisOps: + # check conditionalVisOps to see if this primvar applies + # to this object + expr = conditionalVisOps.get('expr', None) + node = rm + if expr and not eval(expr): + return + + val = getattr(rm, prop_name) + if not val: + return + + if 'inheritable' in meta: + if float(val) == meta['inherit_true_value']: + if inherit_node and hasattr(inherit_node, prop_name): + val = getattr(inherit_node, prop_name) + + ri_name = meta['primvar'] + is_array = False + array_len = -1 + if 'arraySize' in meta: + is_array = True + array_len = meta['arraySize'] + param_type = meta['renderman_type'] + set_rix_param(primvars, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=rm, prop_name=prop_name) + +def set_rioption_bl_prop(options, prop_name, meta, rm): + if 'riopt' not in meta: + return + + val = getattr(rm, prop_name) + ri_name = meta['riopt'] + is_array = False + array_len = -1 + if 'arraySize' in meta: + is_array = True + array_len = meta['arraySize'] + param_type = meta['renderman_type'] + val = string_utils.convert_val(val, type_hint=param_type) + set_rix_param(options, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=rm, prop_name=prop_name) + +def set_riattr_bl_prop(attrs, prop_name, meta, rm, check_inherit=True, remove=True): + if 'riattr' not in meta: + return + + conditionalVisOps = meta.get('conditionalVisOps', None) + if conditionalVisOps: + # check conditionalVisOps to see if this riattr applies + # to this object + expr = conditionalVisOps.get('expr', None) + node = rm + if expr and not eval(expr): + return + + val = getattr(rm, prop_name) + ri_name = meta['riattr'] + if check_inherit and 'inheritable' in meta: + cond = meta['inherit_true_value'] + if isinstance(cond, str): + if exec(cond): + if remove: + attrs.Remove(ri_name) + return + elif float(val) == cond: + if remove: + attrs.Remove(ri_name) + return + + is_array = False + array_len = -1 + if 'arraySize' in meta: + is_array = True + array_len = meta['arraySize'] + param_type = meta['renderman_type'] + val = string_utils.convert_val(val, type_hint=param_type) + set_rix_param(attrs, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=rm, prop_name=prop_name) + def build_output_param_str(rman_sg_node, mat_name, from_node, from_socket, convert_socket=False, param_type=''): + + from . import shadergraph_utils + nodes_to_blnodeinfo = getattr(rman_sg_node, 'nodes_to_blnodeinfo', dict()) if from_node in nodes_to_blnodeinfo: bl_node_info = nodes_to_blnodeinfo[from_node] @@ -267,6 +343,9 @@ def build_output_param_str(rman_sg_node, mat_name, from_node, from_socket, conve return "%s:%s" % (from_node_name, from_sock_name) def get_output_param_str(rman_sg_node, node, mat_name, socket, to_socket=None, param_type='', check_do_convert=True): + + from . import shadergraph_utils + # if this is a node group, hook it up to the input node inside! if node.bl_idname == 'ShaderNodeGroup': ng = node.node_tree @@ -441,18 +520,6 @@ def vstruct_conditional(node, param): new_tokens.extend(['else', 'False']) return eval(" ".join(new_tokens)) -def set_frame_sensitive(rman_sg_node, prop): - # check if the prop value has any frame token - # ex: , , etc. - # if it does, it means we need to issue a material - # update if the frame changes - pat = re.compile(r'<[f|F]\d*>') - m = pat.search(prop) - if m: - rman_sg_node.is_frame_sensitive = True - else: - rman_sg_node.is_frame_sensitive = False - def set_dspymeta_params(node, prop_name, params): if node.plugin_name not in ['openexr', 'deepexr']: # for now, we only accept openexr an deepexr @@ -510,9 +577,15 @@ def set_pxrosl_params(node, rman_sg_node, params, ob=None, mat_name=None): set_rix_param(params, param_type, param_name, val, is_reference=False) def set_ramp_rixparams(node, prop_name, prop, param_type, params): + nt = node.rman_fake_node_group_ptr + ramp_name = prop + if nt and ramp_name not in nt.nodes: + # this shouldn't happen, but sometimes can + # try to look at the bpy.data.node_groups version + nt = bpy.data.node_groups[node.rman_fake_node_group] + if nt and ramp_name not in nt.nodes: + nt = None if param_type == 'colorramp': - nt = node.rman_fake_node_group_ptr - ramp_name = prop if nt: color_ramp_node = nt.nodes[ramp_name] colors = [] @@ -560,9 +633,7 @@ def set_ramp_rixparams(node, prop_name, prop, param_type, params): interp = 'catmull-rom' params.SetString("%s_Interpolation" % prop_name, interp ) - elif param_type == 'floatramp': - nt = node.rman_fake_node_group_ptr - ramp_name = prop + elif param_type == 'floatramp': if nt: float_ramp_node = nt.nodes[ramp_name] @@ -582,8 +653,8 @@ def set_ramp_rixparams(node, prop_name, prop, param_type, params): params.SetFloatArray('%s_Knots' % prop_name, knots, len(knots)) params.SetFloatArray('%s_Floats' % prop_name, vals, len(vals)) - # Blender doesn't have an interpolation selection for float ramps. Default to catmull-rom - interp = 'catmull-rom' + interp_name = '%s_Interpolation' % prop_name + interp = getattr(node, interp_name, 'linear') params.SetString("%s_Interpolation" % prop_name, interp ) else: # this might be from a linked file @@ -608,7 +679,8 @@ def set_ramp_rixparams(node, prop_name, prop, param_type, params): params.SetFloatArray('%s_Knots' % prop_name, knots, len(knots)) params.SetFloatArray('%s_Floats' % prop_name, vals, len(vals)) - interp = 'catmull-rom' + interp_name = '%s_Interpolation' % prop_name + interp = getattr(node, interp_name, 'linear') params.SetString("%s_Interpolation" % prop_name, interp ) def set_array_rixparams(node, rman_sg_node, mat_name, bl_prop_info, prop_name, prop, params): @@ -618,12 +690,14 @@ def set_array_rixparams(node, rman_sg_node, mat_name, bl_prop_info, prop_name, p param_type = bl_prop_info.renderman_array_type param_name = bl_prop_info.renderman_name collection = getattr(node, coll_nm) - - for i in range(len(collection)): + any_connections = False + inputs = getattr(node, 'inputs', dict()) + input_array_size = len(collection) + for i in range(input_array_size): elem = collection[i] nm = '%s[%d]' % (prop_name, i) - if hasattr(node, 'inputs') and nm in node.inputs and \ - node.inputs[nm].is_linked: + if nm in node.inputs and inputs[nm].is_linked: + any_connections = True to_socket = node.inputs[nm] from_socket = to_socket.links[0].from_socket from_node = to_socket.links[0].from_node @@ -631,15 +705,29 @@ def set_array_rixparams(node, rman_sg_node, mat_name, bl_prop_info, prop_name, p val = get_output_param_str(rman_sg_node, from_node, mat_name, from_socket, to_socket, param_type) if val: - val_ref_array.append(val) + if getattr(from_socket, 'is_array', False): + # the socket from the incoming connection is an array + # clear val_ref_array so far and resize it to this + # socket's array size + array_size = from_socket.array_size + val_ref_array.clear() + for i in range(array_size): + cnx_val = '%s[%d]' % (val, i) + val_ref_array.append(cnx_val) + break + val_ref_array.append(val) + else: + val_ref_array.append("") else: prop = getattr(elem, 'value_%s' % param_type) val = string_utils.convert_val(prop, type_hint=param_type) if param_type in RFB_FLOAT3: val_array.extend(val) else: - val_array.append(val) - if val_ref_array: + val_array.append(val) + val_ref_array.append("") + + if any_connections: set_rix_param(params, param_type, param_name, val_ref_array, is_reference=True, is_array=True, array_len=len(val_ref_array)) else: set_rix_param(params, param_type, param_name, val_array, is_reference=False, is_array=True, array_len=len(val_array)) @@ -650,6 +738,7 @@ def set_node_rixparams(node, rman_sg_node, params, ob=None, mat_name=None, group set_pxrosl_params(node, rman_sg_node, params, ob=ob, mat_name=mat_name) return params + is_frame_sensitive = False for prop_name, meta in node.prop_meta.items(): bl_prop_info = BlPropInfo(node, prop_name, meta) param_widget = bl_prop_info.widget @@ -756,8 +845,10 @@ def set_node_rixparams(node, rman_sg_node, params, ob=None, mat_name=None, group val = [0, 0, 0] if param_type == 'color' else 0 elif param_type == 'string': - if rman_sg_node: - set_frame_sensitive(rman_sg_node, prop) + from . import texture_utils + + if not is_frame_sensitive: + is_frame_sensitive = string_utils.check_frame_sensitive(prop) val = string_utils.expand_string(prop) options = meta['options'] @@ -778,7 +869,10 @@ def set_node_rixparams(node, rman_sg_node, params, ob=None, mat_name=None, group array_len = int(bl_prop_info.arraySize) set_rix_param(params, param_type, param_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=node) - + + if rman_sg_node: + rman_sg_node.is_frame_sensitive = (rman_sg_node.is_frame_sensitive or is_frame_sensitive) + return params def property_group_to_rixparams(node, rman_sg_node, sg_node, ob=None, mat_name=None, group_node=None): @@ -792,6 +886,8 @@ def portal_inherit_dome_params(portal_node, dome, dome_node, rixparams): it is parented to. ''' + from . import texture_utils + inheritAttrs = { "float specular": 1.0, "float diffuse": 1.0, diff --git a/rfb_utils/register_utils.py b/rfb_utils/register_utils.py index 7d879e0e..a757f2ac 100644 --- a/rfb_utils/register_utils.py +++ b/rfb_utils/register_utils.py @@ -3,6 +3,8 @@ def rman_register_class(cls): try: + if hasattr(bpy.types, str(cls)): + rman_unregister_class(cls) bpy.utils.register_class(cls) except ValueError as e: rfb_log().debug("Could not register class, %s, because: %s" % (str(cls), str(e))) diff --git a/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py b/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py index 9e3267b1..81e8d52c 100644 --- a/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py +++ b/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py @@ -11,7 +11,9 @@ NodeDescParam.optional_attrs = NodeDescParam.optional_attrs + [] NodeDescParamJSON.keywords = NodeDescParamJSON.keywords + ['panel', 'inheritable', 'inherit_true_value', 'update_function_name', 'update_function', - 'readOnly', 'always_write'] + 'set_function_name', 'set_function', + 'get_function_name', 'get_function', + 'readOnly', 'always_write', 'ipr_editable'] def blender_finalize(obj): """Post-process some parameters for Blender. @@ -23,7 +25,7 @@ def blender_finalize(obj): if hasattr(obj, 'help'): obj.help = obj.help.replace('\\"', '"') - obj.help = obj.help.replace("'", "\\'") + #obj.help = obj.help.replace("'", "\\'") obj.help = obj.help.replace('
', '\n') class RfbNodeDescParamXML(NodeDescParamXML): diff --git a/rfb_utils/rman_socket_utils.py b/rfb_utils/rman_socket_utils.py index 0a1ba31a..e1bd674e 100644 --- a/rfb_utils/rman_socket_utils.py +++ b/rfb_utils/rman_socket_utils.py @@ -35,6 +35,7 @@ def node_add_input(node, param_type, param_name, meta, param_label): socket = node.inputs.new( __RMAN_SOCKET_MAP__[param_type], param_name, identifier=param_label) socket.link_limit = 1 + socket.rman_label = param_label if param_type in ['struct', 'vstruct', 'void']: socket.hide_value = True @@ -117,4 +118,16 @@ def node_add_outputs(node): socket.renderman_type = rman_type if rman_type == 'struct': struct_name = meta.get('struct_name', 'Manifold') - socket.struct_name = struct_name \ No newline at end of file + socket.struct_name = struct_name + + arraySize = meta['arraySize'] + if arraySize > 0: + # this is an array + # add separate scokets for each element + socket.is_array = True + socket.array_size = arraySize + for i in range(0, arraySize): + elem_nm = '%s[%d]' % (name, i) + elem_socket = node.outputs.new(__RMAN_SOCKET_MAP__[rman_type], elem_nm) + elem_socket.renderman_type = rman_type + elem_socket.array_elem = i \ No newline at end of file diff --git a/rfb_utils/scene_utils.py b/rfb_utils/scene_utils.py index 2f5cad88..4a325557 100644 --- a/rfb_utils/scene_utils.py +++ b/rfb_utils/scene_utils.py @@ -1,6 +1,8 @@ from . import shadergraph_utils from . import object_utils from . import prefs_utils +from . import string_utils +from ..rman_constants import RMAN_GLOBAL_VOL_AGGREGATE from ..rfb_logger import rfb_log import bpy import sys @@ -45,6 +47,47 @@ def get_renderman_layer(context): return rm_rl +def add_global_vol_aggregate(): + ''' + Checks to see if the global volume aggregate exists. + If it doesn't exists, we add it. + ''' + bl_scene = bpy.context.scene + rm = bl_scene.renderman + if len(rm.vol_aggregates) > 0: + vol_agg = rm.vol_aggregates[0] + if vol_agg.name == RMAN_GLOBAL_VOL_AGGREGATE: + return + vol_agg = rm.vol_aggregates.add() + vol_agg.name = RMAN_GLOBAL_VOL_AGGREGATE + rm.vol_aggregates.move(len(rm.vol_aggregates)-1, 0) + + +def should_use_bl_compositor(bl_scene): + ''' + Check if we should use the Blender compositor + + Args: + bl_scene (bpy.types.Scene) - the Blender scene + + Returns: + (bool) - true if we should use the compositor; false if not + ''' + from . import display_utils + + rm = bl_scene.renderman + if not bpy.app.background: + return (rm.render_into == 'blender') + + if not display_utils.using_rman_displays(): + return True + + if not rm.use_bl_compositor: + # explicitiy turned off + return False + + return bl_scene.use_nodes and bl_scene.render.use_compositing + def any_areas_shading(): ''' Loop through all of the windows/areas and return True if any of @@ -430,6 +473,68 @@ def find_node_by_name(node_name, ob_name, library=''): return (None, None) +def set_lightlinking_properties(ob, light_ob, illuminate, update_light=True): + light_props = shadergraph_utils.get_rman_light_properties_group(light_ob) + if light_props.renderman_light_role not in {'RMAN_LIGHTFILTER', 'RMAN_LIGHT'}: + return + + if update_light: + light_ob.update_tag(refresh={'DATA'}) + changed = False + if light_props.renderman_light_role == 'RMAN_LIGHT': + exclude_subset = [] + if illuminate == 'OFF': + do_add = True + for j, subset in enumerate(ob.renderman.rman_lighting_excludesubset): + if subset.light_ob == light_ob: + do_add = False + exclude_subset.append('%s' % string_utils.sanitize_node_name(subset.light_ob.name_full)) + if do_add: + subset = ob.renderman.rman_lighting_excludesubset.add() + subset.name = light_ob.name + subset.light_ob = light_ob + changed = True + exclude_subset.append('%s' % string_utils.sanitize_node_name(light_ob.name_full)) + else: + idx = -1 + for j, subset in enumerate(ob.renderman.rman_lighting_excludesubset): + if subset.light_ob == light_ob: + changed = True + idx = j + else: + exclude_subset.append('%s' % string_utils.sanitize_node_name(subset.light_ob.name_full)) + if changed: + ob.renderman.rman_lighting_excludesubset.remove(j) + ob.renderman.rman_lighting_excludesubset_string = ','.join(exclude_subset) + else: + lightfilter_subset = [] + if illuminate == 'OFF': + do_add = True + for j, subset in enumerate(ob.renderman.rman_lightfilter_subset): + if subset.light_ob == light_ob: + do_add = False + lightfilter_subset.append('-%s' % string_utils.sanitize_node_name(subset.light_ob.name_full)) + + if do_add: + subset = ob.renderman.rman_lightfilter_subset.add() + subset.name = light_ob.name + subset.light_ob = light_ob + changed = True + lightfilter_subset.append('-%s' % string_utils.sanitize_node_name(light_ob.name_full)) + else: + idx = -1 + for j, subset in enumerate(ob.renderman.rman_lightfilter_subset): + if subset.light_ob == light_ob: + changed = True + idx = j + else: + lightfilter_subset.append('-%s' % string_utils.sanitize_node_name(subset.light_ob.name_full)) + if changed: + ob.renderman.rman_lightfilter_subset.remove(idx) + ob.renderman.rman_lightfilter_subset_string = ','.join(lightfilter_subset) + + return changed + def is_renderable(scene, ob): return (is_visible_layer(scene, ob) and not ob.hide_render) or \ (ob.type in ['ARMATURE', 'LATTICE', 'EMPTY'] and ob.instance_type not in SUPPORTED_DUPLI_TYPES) diff --git a/rfb_utils/scenegraph_utils.py b/rfb_utils/scenegraph_utils.py index 4e0bec6d..2c482872 100644 --- a/rfb_utils/scenegraph_utils.py +++ b/rfb_utils/scenegraph_utils.py @@ -7,6 +7,8 @@ def set_material(sg_node, sg_material_node): sg_material_node (RixSGMaterial) - the scene graph material node ''' + if sg_material_node is None: + return sg_node.SetMaterial(sg_material_node) attrs = sg_node.GetAttributes() @@ -18,23 +20,40 @@ def update_sg_integrator(context): rr = rman_render.RmanRender.get_rman_render() rr.rman_scene_sync.update_integrator(context) -def update_sg_options(context): +def update_sg_options(prop_name, context): from .. import rman_render rr = rman_render.RmanRender.get_rman_render() - rr.rman_scene_sync.update_global_options(context) + rr.rman_scene_sync.update_global_options(prop_name, context) -def update_sg_root_node(context): +def update_sg_root_node(prop_name, context): from .. import rman_render rr = rman_render.RmanRender.get_rman_render() - rr.rman_scene_sync.update_root_node_func(context) + rr.rman_scene_sync.update_root_node_func(prop_name, context) + +def update_sg_node_riattr(prop_name, context, bl_object=None): + from .. import rman_render + rr = rman_render.RmanRender.get_rman_render() + rr.rman_scene_sync.update_sg_node_riattr(prop_name, context, bl_object=bl_object) + +def update_sg_node_primvar(prop_name, context, bl_object=None): + from .. import rman_render + rr = rman_render.RmanRender.get_rman_render() + rr.rman_scene_sync.update_sg_node_primvar(prop_name, context, bl_object=bl_object) def export_vol_aggregate(bl_scene, primvar, ob): vol_aggregate_group = [] - for v in bl_scene.renderman.vol_aggregates: + for i,v in enumerate(bl_scene.renderman.vol_aggregates): + if i == 0: + continue for member in v.members: if member.ob_pointer.original == ob.original: vol_aggregate_group.append(v.name) break if vol_aggregate_group: - primvar.SetStringArray("volume:aggregate", vol_aggregate_group, len(vol_aggregate_group)) \ No newline at end of file + primvar.SetStringArray("volume:aggregate", vol_aggregate_group, len(vol_aggregate_group)) + elif ob.renderman.volume_global_aggregate: + # we assume the first group is the global aggregate + primvar.SetStringArray("volume:aggregate", [bl_scene.renderman.vol_aggregates[0].name], 1) + else: + primvar.SetStringArray("volume:aggregate", [""], 1) \ No newline at end of file diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index b2402305..0c19b99b 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -2,7 +2,7 @@ from . import filepath_utils from . import string_utils from . import object_utils -from . import scene_utils +from .prefs_utils import get_pref from ..rman_constants import RMAN_STYLIZED_FILTERS, RMAN_STYLIZED_PATTERNS, RMAN_UTILITY_PATTERN_NAMES, RFB_FLOAT3 import math import bpy @@ -259,6 +259,58 @@ def is_socket_float3_type(socket): else: return socket.type in ['RGBA', 'VECTOR'] +def set_solo_node(node, nt, solo_node_name, refresh_solo=False, solo_node_output=''): + def hide_all(nt, node): + if not get_pref('rman_solo_collapse_nodes'): + return + for n in nt.nodes: + hide = (n != node) + if hasattr(n, 'prev_hidden'): + setattr(n, 'prev_hidden', n.hide) + n.hide = hide + for input in n.inputs: + if not input.is_linked: + if hasattr(input, 'prev_hidden'): + setattr(input, 'prev_hidden', input.hide) + input.hide = hide + + for output in n.outputs: + if not output.is_linked: + if hasattr(output, 'prev_hidden'): + setattr(output, 'prev_hidden', output.hide) + output.hide = hide + + def unhide_all(nt): + if not get_pref('rman_solo_collapse_nodes'): + return + for n in nt.nodes: + hide = getattr(n, 'prev_hidden', False) + n.hide = hide + for input in n.inputs: + if not input.is_linked: + hide = getattr(input, 'prev_hidden', False) + input.hide = hide + + for output in n.outputs: + if not output.is_linked: + hide = getattr(output, 'prev_hidden', False) + output.hide = hide + + if refresh_solo: + node.solo_nodetree = None + node.solo_node_name = '' + node.solo_node_output = '' + unhide_all(nt) + return + + if solo_node_name: + node.solo_nodetree = nt + node.solo_node_name = solo_node_name + node.solo_node_output = solo_node_output + solo_node = nt.nodes[solo_node_name] + hide_all(nt, solo_node) + + # do we need to convert this socket? def do_convert_socket(from_socket, to_socket): if not to_socket: @@ -477,6 +529,8 @@ def get_all_shading_nodes(scene=None): (list) - list of all the shading nodes ''' + from . import scene_utils + nodes = list() if not scene: @@ -844,6 +898,42 @@ def has_stylized_pattern_node(ob, node=None): return False +def hide_cycles_nodes(id): + cycles_output_node = None + if isinstance(id, bpy.types.Material): + cycles_output_node = find_node(id, 'ShaderNodeOutputMaterial') + elif isinstance(id, bpy.types.Light): + cycles_output_node = find_node(id, 'ShaderNodeOutputLight') + elif isinstance(id, bpy.types.World): + cycles_output_node = find_node(id, 'ShaderNodeOutputWorld') + if not cycles_output_node: + return + cycles_output_node.hide = True + for i in cycles_output_node.inputs: + if i.is_linked: + i.links[0].from_node.hide = True + + +def create_bxdf(bxdf): + mat = bpy.data.materials.new(bxdf) + mat.use_nodes = True + nt = mat.node_tree + hide_cycles_nodes(mat) + + output = nt.nodes.new('RendermanOutputNode') + default = nt.nodes.new('%sBxdfNode' % bxdf) + default.location = output.location + default.location[0] -= 300 + nt.links.new(default.outputs[0], output.inputs[0]) + output.inputs[1].hide = True + output.inputs[3].hide = True + default.update_mat(mat) + + if bxdf == 'PxrLayerSurface': + create_pxrlayer_nodes(nt, default) + + return mat + def create_pxrlayer_nodes(nt, bxdf): from .. import rman_bl_nodes diff --git a/rfb_utils/string_expr.py b/rfb_utils/string_expr.py index 7e69f139..50ca7bd4 100644 --- a/rfb_utils/string_expr.py +++ b/rfb_utils/string_expr.py @@ -145,18 +145,32 @@ def update_blend_tokens(self): # @time_this def set_frame_context(self, frame): """Set the scene frame for the next subst() call.""" - self.tokens['frame'] = str(frame) - iframe = int(frame) - self.tokens['f'] = str(iframe) - self.tokens['f2'] = '{:0>2d}'.format(iframe) - self.tokens['f3'] = '{:0>3d}'.format(iframe) - self.tokens['f4'] = '{:0>4d}'.format(iframe) - self.tokens['f5'] = '{:0>5d}'.format(iframe) - self.tokens['F'] = str(iframe) - self.tokens['F2'] = '{:0>2d}'.format(iframe) - self.tokens['F3'] = '{:0>3d}'.format(iframe) - self.tokens['F4'] = '{:0>4d}'.format(iframe) - self.tokens['F5'] = '{:0>5d}'.format(iframe) + + if isinstance(frame, int): + self.tokens['frame'] = str(frame) + iframe = int(frame) + self.tokens['f'] = str(iframe) + self.tokens['f2'] = '{:0>2d}'.format(iframe) + self.tokens['f3'] = '{:0>3d}'.format(iframe) + self.tokens['f4'] = '{:0>4d}'.format(iframe) + self.tokens['f5'] = '{:0>5d}'.format(iframe) + self.tokens['F'] = str(iframe) + self.tokens['F2'] = '{:0>2d}'.format(iframe) + self.tokens['F3'] = '{:0>3d}'.format(iframe) + self.tokens['F4'] = '{:0>4d}'.format(iframe) + self.tokens['F5'] = '{:0>5d}'.format(iframe) + elif isinstance(frame, str): + self.tokens['frame'] = frame + self.tokens['f'] = frame + self.tokens['f2'] = '%s' % (frame) * 2 + self.tokens['f3'] = '%s' % (frame) * 3 + self.tokens['f4'] = '%s' % (frame) * 4 + self.tokens['f5'] = '%s' % (frame) * 5 + self.tokens['F'] = frame + self.tokens['F2'] = '%s' % (frame) * 2 + self.tokens['F3'] = '%s' % (frame) * 3 + self.tokens['F4'] = '%s' % (frame) * 4 + self.tokens['F5'] = '%s' % (frame) * 5 # @time_this def expand(self, expr, objTokens={}, asFilePath=False): diff --git a/rfb_utils/string_utils.py b/rfb_utils/string_utils.py index 681c87da..06f1266c 100644 --- a/rfb_utils/string_utils.py +++ b/rfb_utils/string_utils.py @@ -34,7 +34,7 @@ def expand(self, string, display=None, frame=None, token_dict = dict(), asFilePa Kwargs: - display (str): The display being considered. This is necessary if your expression contains or - - frame (int): An optional frame number to expand , , etc. + - frame (int, str): An optional frame number to expand , , etc. Returns: - The expanded string @@ -110,7 +110,7 @@ def expand_string(string, display=None, glob_sequence=False, frame=None, token_d Kwargs: - display (str): the name of a display driver to update tokens. - - frame (str): the frame to use for expanding + - frame (int, str): the frame to use for expanding. If a string, the string will be repeated by the paddning. Ex: '#' will turn to '####' for - token_dict (dict): dictionary of token/vals that also need to be set. - asFilePath (bool): treat the input string as a path. Will create directories if they don't exist @@ -163,6 +163,10 @@ def get_var(nm): converter_validity_check() return __SCENE_STRING_CONVERTER__.get_token(nm) +def update_frame_token(frame): + converter_validity_check() + __SCENE_STRING_CONVERTER__.expr.set_frame_context(frame) + def get_tokenized_openvdb_file(frame_filepath, grids_frame): openvdb_file = filepath_utils.get_real_path(frame_filepath) f = os.path.basename(frame_filepath) @@ -212,6 +216,17 @@ def update_blender_tokens_cb(bl_scene): __SCENE_STRING_CONVERTER__.update(bl_scene=scene) +def check_frame_sensitive(s): + # check if the sting has any frame token + # ex: , , etc. + # if it does, it means we need to issue a material + # update if the frame changes + pat = re.compile(r'<[f|F]\d*>') + m = pat.search(s) + if m: + return True + return False + def _format_time_(seconds): hours = seconds // (60 * 60) seconds %= (60 * 60) @@ -222,28 +237,42 @@ def _format_time_(seconds): def convert_val(v, type_hint=None): import mathutils + converted_val = v + # float, int if type_hint == 'color': - return list(v)[:3] + converted_val = list(v)[:3] - if type(v) in (mathutils.Vector, mathutils.Color) or\ + elif type(v) in (mathutils.Vector, mathutils.Color) or\ v.__class__.__name__ == 'bpy_prop_array'\ or v.__class__.__name__ == 'Euler': - # BBM modified from if to elif - return list(v) + converted_val = list(v) + + elif type(v) == str and v.startswith('['): + converted_val = eval(v) + + elif type(v) == list: + converted_val = v # matrix elif type(v) == mathutils.Matrix: - return [v[0][0], v[1][0], v[2][0], v[3][0], + converted_val = [v[0][0], v[1][0], v[2][0], v[3][0], v[0][1], v[1][1], v[2][1], v[3][1], v[0][2], v[1][2], v[2][2], v[3][2], v[0][3], v[1][3], v[2][3], v[3][3]] elif type_hint == 'int': - return int(v) + converted_val = int(v) elif type_hint == 'float': - return float(v) - else: - return v + converted_val = float(v) + + if type_hint == 'string': + if isinstance(converted_val, list): + for i in range(len(converted_val)): + converted_val[i] = expand_string(converted_val[i], asFilePath=True) + else: + converted_val = expand_string(converted_val, asFilePath=True) + + return converted_val def getattr_recursive(ptr, attrstring): for attr in attrstring.split("."): diff --git a/rfb_utils/texture_utils.py b/rfb_utils/texture_utils.py index 020924eb..2e40d7cb 100644 --- a/rfb_utils/texture_utils.py +++ b/rfb_utils/texture_utils.py @@ -96,10 +96,7 @@ def get_ext_list(self): return ext_list def host_token_resolver_func(self, outpath): - if self.rman_scene: - outpath = string_utils.expand_string(outpath, frame=self.rman_scene.bl_frame_current, asFilePath=True) - else: - outpath = string_utils.expand_string(outpath, asFilePath=True) + outpath = string_utils.expand_string(outpath, asFilePath=True) return outpath def done_callback(self, nodeID, txfile): @@ -123,16 +120,10 @@ def get_output_tex(self, txfile): ''' if txfile.state in (txmanager.STATE_EXISTS, txmanager.STATE_IS_TEX): output_tex = txfile.get_output_texture() - if self.rman_scene: - output_tex = string_utils.expand_string(output_tex, frame=self.rman_scene.bl_frame_current, asFilePath=True) - else: - output_tex = string_utils.expand_string(output_tex, asFilePath=True) + output_tex = string_utils.expand_string(output_tex, asFilePath=True) elif txfile.state == txmanager.STATE_INPUT_MISSING: output_tex = txfile.input_image - if self.rman_scene: - output_tex = string_utils.expand_string(output_tex, frame=self.rman_scene.bl_frame_current, asFilePath=True) - else: - output_tex = string_utils.expand_string(output_tex, asFilePath=True) + output_tex = string_utils.expand_string(output_tex, asFilePath=True) else: output_tex = self.txmanager.get_placeholder_tex() @@ -229,10 +220,7 @@ def is_file_src_tex(self, node, prop_name): return False def does_file_exist(self, file_path): - if self.rman_scene: - outpath = string_utils.expand_string(file_path, frame=self.rman_scene.bl_frame_current, asFilePath=True) - else: - outpath = string_utils.expand_string(file_path, asFilePath=True) + outpath = string_utils.expand_string(file_path, asFilePath=True) if os.path.exists(outpath): return True @@ -437,19 +425,17 @@ def txmanager_pre_save_cb(bl_scene): return get_txmanager().txmanager.save_state() -@persistent -def depsgraph_handler(bl_scene, depsgraph): - for update in depsgraph.updates: - id = update.id - # check new linked in materials - if id.library: - link_file_handler(id) - continue - # check if nodes were renamed - elif isinstance(id, bpy.types.Object): - check_node_rename(id) - elif isinstance(id, bpy.types.Material): - check_node_rename(id) +def depsgraph_handler(depsgraph_update, depsgraph): + id = depsgraph_update.id + # check new linked in materials + if id.library: + link_file_handler(id) + return + # check if nodes were renamed + elif isinstance(id, bpy.types.Object): + check_node_rename(id) + elif isinstance(id, bpy.types.Material): + check_node_rename(id) def check_node_rename(id): nodes_list = list() diff --git a/rfb_utils/timer_utils.py b/rfb_utils/timer_utils.py new file mode 100644 index 00000000..c3bacf1b --- /dev/null +++ b/rfb_utils/timer_utils.py @@ -0,0 +1,19 @@ +from .envconfig_utils import envconfig +import time + +def time_this(f): + if not envconfig().getenv('RFB_DEVELOPER'): + return f + """Function that can be used as a decorator to time any method.""" + def timed(*args, **kw): + tstart = time.time() + result = f(*args, **kw) + tstop = time.time() + elapsed = (tstop - tstart) * 1000.0 + print(' _ %0.2f ms: %s(%s, %s)' % ( + elapsed, f.__name__, + ', '.join([repr(a) for a in args]), + ', '.join(['%s=%r' % (k, w) for k, w in kw.items()]))) + return result + + return timed \ No newline at end of file diff --git a/rfb_utils/transform_utils.py b/rfb_utils/transform_utils.py index 8a81eb99..4a5b9df0 100644 --- a/rfb_utils/transform_utils.py +++ b/rfb_utils/transform_utils.py @@ -1,5 +1,5 @@ import rman -from mathutils import Matrix +from mathutils import Matrix,Vector def convert_matrix(m): v = [m[0][0], m[1][0], m[2][0], m[3][0], @@ -21,6 +21,25 @@ def convert_matrix4x4(m): return rman_mtx +def get_world_bounding_box(selected_obs): + + min_vector = None + max_vector = None + + for ob in selected_obs: + v = ob.matrix_world @ Vector(ob.bound_box[0]) + if min_vector is None: + min_vector = v + elif v < min_vector: + min_vector = v + v = ob.matrix_world @ Vector(ob.bound_box[7]) + if max_vector is None: + max_vector = v + elif v > max_vector: + max_vector = v + + return "%f %f %f %f %f %f" % (min_vector[0], max_vector[0], min_vector[1], max_vector[1], min_vector[2], max_vector[2]) + def convert_ob_bounds(ob_bb): return (ob_bb[0][0], ob_bb[7][0], ob_bb[0][1], ob_bb[7][1], ob_bb[0][2], ob_bb[1][2]) diff --git a/rfb_utils/upgrade_utils.py b/rfb_utils/upgrade_utils.py index 005b3fef..409ae2aa 100644 --- a/rfb_utils/upgrade_utils.py +++ b/rfb_utils/upgrade_utils.py @@ -22,10 +22,207 @@ def upgrade_243(scene): elem.name = '%s[%d]' % (prop_name, len(collection)-1) elem.type = param_array_type +def upgrade_250(scene): + ''' + Rename input/output sockets: + Bxdf -> bxdf_in/bxdf_out + Light -> light_in/light_out + Displacement -> displace_in/displace_out + LightFilter -> lightfilter_in/lightfilter_out + Integrator -> integrator_in/integrator_out + Projection -> projection_in/projection_out + DisplayFilter -> displayfilter_in/displayfilter_out + SampleFilter -> samplefilter_in/samplefilter_out + + Add: color ramp to PxrStylizedToon + + ''' + for node in shadergraph_utils.get_all_shading_nodes(scene=scene): + renderman_node_type = getattr(node, 'renderman_node_type', '') + if renderman_node_type in ['bxdf', 'projection', 'light', 'integrator']: + old_name = renderman_node_type.capitalize() + if old_name in node.outputs: + node.outputs[old_name].name = '%s_out' % renderman_node_type + elif renderman_node_type in ['displace', 'displacement']: + if 'Displacement' in node.outputs: + node.outputs['Displacement'].name = 'displace_out' + elif renderman_node_type == 'lightfilter': + if 'LightFilter' in node.outputs: + node.outputs['LightFilter'].name = '%s_out' % renderman_node_type + elif renderman_node_type == 'samplefilter': + if 'SampleFilter' in node.outputs: + node.outputs['SampleFilter'].name = '%s_out' % renderman_node_type + elif renderman_node_type == 'displayfilter': + if 'DisplayFilter' in node.outputs: + node.outputs['DisplayFilter'].name = '%s_out' % renderman_node_type + + if node.bl_label == 'PxrStylizedToon': + nt = node.rman_fake_node_group_ptr + n = nt.nodes.new('ShaderNodeValToRGB') + setattr(node, 'colorRamp', n.name) + + + for mat in bpy.data.materials: + output = shadergraph_utils.find_node(mat, 'RendermanOutputNode') + if output: + if 'Bxdf' in output.inputs: + output.inputs['Bxdf'].name = 'bxdf_in' + if 'Light' in output.inputs: + output.inputs['Light'].name = 'light_in' + if 'Displacement' in output.inputs: + output.inputs['Displacement'].name = 'displace_in' + if 'LightFilter' in output.inputs: + output.inputs['LightFilter'].name = 'lightfilter_in' + + for light in bpy.data.lights: + output = shadergraph_utils.is_renderman_nodetree(light) + if output: + if 'Light' in output.inputs: + output.inputs['Light'].name = 'light_in' + if 'LightFilter' in output.inputs: + output.inputs['LightFilter'].name = 'lightfilter_in' + + for world in bpy.data.worlds: + output = shadergraph_utils.find_node(world, 'RendermanIntegratorsOutputNode') + if output: + if 'Integrator' in output.inputs: + output.inputs['Integrator'].name = 'integrator_in' + + output = shadergraph_utils.find_node(world, 'RendermanSamplefiltersOutputNode') + if output: + for i, o in enumerate(output.inputs): + o.name = 'samplefilter_in[%d]' % i + + output = shadergraph_utils.find_node(world, 'RendermanDisplayfiltersOutputNode') + if output: + for i, o in enumerate(output.inputs): + o.name = 'displayfilter_in[%d]' % i + + for camera in bpy.data.cameras: + nt = camera.renderman.rman_nodetree + if nt: + output = shadergraph_utils.find_node_from_nodetree(nt, 'RendermanProjectionsOutputNode') + if 'Projection' in output.inputs: + output.inputs['Projection'].name = 'projection_in' + +def upgrade_250_1(scene): + ''' + Upgrade lama nodes + + 25.0b2 changed the names of input parameters for lama nodes, + because they were using OSL reserved keywords (ex: color) + ''' + + def copy_param(old_node, new_node, old_nm, new_nm): + socket = old_node.inputs.get(old_nm, None) + if socket and socket.is_linked: + connected_socket = socket.links[0].from_socket + nt.links.new(connected_socket, new_node.inputs[new_nm]) + else: + setattr(new_node, new_nm, getattr(n, old_nm)) + + for mat in bpy.data.materials: + if mat.node_tree is None: + continue + nt = mat.node_tree + nodes = [n for n in nt.nodes] + for n in nodes: + new_node = None + if n.bl_label == 'LamaDiffuse': + new_node = nt.nodes.new('LamaDiffuseBxdfNode') + nms = ['color', 'normal'] + copy_param(n, new_node, 'color', 'diffuseColor') + copy_param(n, new_node, 'normal', 'diffuseNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaSheen': + new_node = nt.nodes.new('LamaSheenBxdfNode') + nms = ['color', 'normal'] + copy_param(n, new_node, 'color', 'sheenColor') + copy_param(n, new_node, 'normal', 'sheenNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaConductor': + new_node = nt.nodes.new('LamaConductorBxdfNode') + nms = ['normal'] + copy_param(n, new_node, 'normal', 'conductorNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaDielectric': + new_node = nt.nodes.new('LamaDielectricBxdfNode') + nms = ['normal'] + copy_param(n, new_node, 'normal', 'dielectricNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaEmission': + new_node = nt.nodes.new('LamaEmissionBxdfNode') + nms = ['color'] + copy_param(n, new_node, 'color', 'emissionColor') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaGeneralizedSchlick': + new_node = nt.nodes.new('LamaGeneralizedSchlickBxdfNode') + nms = ['normal'] + copy_param(n, new_node, 'normal', 'genSchlickNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaSSS': + new_node = nt.nodes.new('LamaSSSBxdfNode') + nms = ['color', 'normal'] + copy_param(n, new_node, 'color', 'sssColor') + copy_param(n, new_node, 'normal', 'sssNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaTranslucent': + new_node = nt.nodes.new('LamaTranslucentBxdfNode') + nms = ['color', 'normal'] + copy_param(n, new_node, 'color', 'translucentColor') + copy_param(n, new_node, 'normal', 'translucentNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + elif n.bl_label == 'LamaTricolorSSS': + new_node = nt.nodes.new('LamaTricolorSSSBxdfNode') + nms = ['normal'] + copy_param(n, new_node, 'normal', 'sssNormal') + for prop_name, meta in n.prop_meta.items(): + if prop_name in nms: + continue + copy_param(n, new_node, prop_name, prop_name) + + if new_node: + new_node.location[0] = n.location[0] + new_node.location[1] = n.location[1] + if n.outputs['bxdf_out'].is_linked: + for link in n.outputs['bxdf_out'].links: + connected_socket = link.to_socket + nt.links.new(new_node.outputs['bxdf_out'], connected_socket) + node_name = n.name + nt.nodes.remove(n) + new_node.name = node_name + new_node.select = False + __RMAN_SCENE_UPGRADE_FUNCTIONS__ = OrderedDict() __RMAN_SCENE_UPGRADE_FUNCTIONS__['24.2'] = upgrade_242 __RMAN_SCENE_UPGRADE_FUNCTIONS__['24.3'] = upgrade_243 +__RMAN_SCENE_UPGRADE_FUNCTIONS__['25.0'] = upgrade_250 +__RMAN_SCENE_UPGRADE_FUNCTIONS__['25.0.1'] = upgrade_250_1 def upgrade_scene(bl_scene): global __RMAN_SCENE_UPGRADE_FUNCTIONS__ @@ -38,13 +235,53 @@ def upgrade_scene(bl_scene): # we started adding a renderman_version property in 24.1 version = '24.1' + scene_major = None + scene_minor = None + scene_patch = None + tokens = version.split('.') + scene_major = tokens[0] + scene_minor = tokens[1] + if len(tokens) > 2: + scene_patch = tokens[2] + for version_str, fn in __RMAN_SCENE_UPGRADE_FUNCTIONS__.items(): - if version < version_str: + upgrade_major = None + upgrade_minor = None + upgrade_patch = None + tokens = version_str.split('.') + upgrade_major = tokens[0] + upgrade_minor = tokens[1] + if len(tokens) > 2: + upgrade_patch = tokens[2] + + if scene_major < upgrade_major: + rfb_log().debug('Upgrade scene to %s' % version_str) + fn(scene) + continue + + if scene_major == upgrade_major and scene_minor < upgrade_minor: + rfb_log().debug('Upgrade scene to %s' % version_str) + fn(scene) + continue + + if not scene_patch and not upgrade_patch: + continue + + if not scene_patch and upgrade_patch: + # The scene version doesn't include a patch version + # This is probably from an older version i.e.: < 25.0b1 + rfb_log().debug('Upgrade scene to %s' % version_str) + fn(scene) + continue + + if upgrade_patch and scene_patch < upgrade_patch: rfb_log().debug('Upgrade scene to %s' % version_str) fn(scene) + + scene.renderman.renderman_version = rman_constants.RFB_SCENE_VERSION_STRING def update_version(bl_scene): if bpy.context.engine != 'PRMAN_RENDER': return for scene in bpy.data.scenes: - scene.renderman.renderman_version = rman_constants.RMAN_SUPPORTED_VERSION_STRING \ No newline at end of file + scene.renderman.renderman_version = rman_constants.RFB_SCENE_VERSION_STRING \ No newline at end of file diff --git a/rman_bl_nodes/__init__.py b/rman_bl_nodes/__init__.py index 32f43533..dc12c717 100644 --- a/rman_bl_nodes/__init__.py +++ b/rman_bl_nodes/__init__.py @@ -17,6 +17,7 @@ from ..rman_properties import rman_properties_camera from ..rman_constants import RFB_ARRAYS_MAX_LEN from ..rman_constants import CYCLES_NODE_MAP +from ..rman_constants import RMAN_FAKE_NODEGROUP from nodeitems_utils import NodeCategory, NodeItem from collections import OrderedDict from bpy.props import * @@ -266,6 +267,10 @@ def class_generate_properties(node, parent_name, node_desc): output_prop_meta['vstruct'] = True if hasattr(node_desc_param, 'struct_name'): output_prop_meta['struct_name'] = node_desc_param.struct_name + if node_desc_param.is_array(): + output_prop_meta['arraySize'] = node_desc_param.size + else: + output_prop_meta['arraySize'] = -1 output_prop_meta['name'] = node_desc_param.name output_meta[prop_name] = output_prop_meta output_meta[prop_name]['renderman_type'] = renderman_type @@ -326,29 +331,29 @@ def generate_node_type(node_desc, is_oso=False): def init(self, context): # add input/output sockets to nodes, based on type if self.renderman_node_type == 'bxdf': - self.outputs.new('RendermanNodeSocketBxdf', "Bxdf") + self.outputs.new('RendermanNodeSocketBxdf', "bxdf_out", identifier="Bxdf") node_add_inputs(self, name, self.prop_names) node_add_outputs(self) elif self.renderman_node_type == 'light': node_add_inputs(self, name, self.prop_names) - self.outputs.new('RendermanNodeSocketLight', "Light") + self.outputs.new('RendermanNodeSocketLight', "light_out", identifier="Light") elif self.renderman_node_type == 'lightfilter': node_add_inputs(self, name, self.prop_names) - self.outputs.new('RendermanNodeSocketLightFilter', "LightFilter") + self.outputs.new('RendermanNodeSocketLightFilter', "lightfilter_out", identifier="LightFilter") elif self.renderman_node_type == 'displace': - self.outputs.new('RendermanNodeSocketDisplacement', "Displacement") + self.outputs.new('RendermanNodeSocketDisplacement', "displace_out", identifier="Displacement") node_add_inputs(self, name, self.prop_names) elif self.renderman_node_type == 'displayfilter': - self.outputs.new('RendermanNodeSocketDisplayFilter', "DisplayFilter") + self.outputs.new('RendermanNodeSocketDisplayFilter', "displayfilter_out", identifier="DisplayFilter") node_add_inputs(self, name, self.prop_names) elif self.renderman_node_type == 'samplefilter': - self.outputs.new('RendermanNodeSocketSampleFilter', "SampleFilter") + self.outputs.new('RendermanNodeSocketSampleFilter', "samplefilter_out", identifier="SampleFilter") node_add_inputs(self, name, self.prop_names) elif self.renderman_node_type == 'integrator': - self.outputs.new('RendermanNodeSocketIntegrator', "Integrator") + self.outputs.new('RendermanNodeSocketIntegrator', "integrator_out", identifier="Integrator") node_add_inputs(self, name, self.prop_names) elif self.renderman_node_type == 'projection': - self.outputs.new('RendermanNodeSocketProjection', "Projection") + self.outputs.new('RendermanNodeSocketProjection', "projection_out", identifier="Projection") node_add_inputs(self, name, self.prop_names) elif name == "PxrOSL": self.outputs.clear() @@ -363,7 +368,7 @@ def init(self, context): if color_rman_ramps or float_rman_ramps: node_group = bpy.data.node_groups.new( - '.__RMAN_FAKE_NODEGROUP__', 'ShaderNodeTree') + RMAN_FAKE_NODEGROUP, 'ShaderNodeTree') node_group.use_fake_user = True self.rman_fake_node_group_ptr = node_group self.rman_fake_node_group = node_group.name diff --git a/rman_bl_nodes/rman_bl_nodes_ops.py b/rman_bl_nodes/rman_bl_nodes_ops.py index 83ae078c..153689f5 100644 --- a/rman_bl_nodes/rman_bl_nodes_ops.py +++ b/rman_bl_nodes/rman_bl_nodes_ops.py @@ -5,6 +5,7 @@ from ..rfb_utils.prefs_utils import get_pref from ..rman_constants import RMAN_BL_NODE_DESCRIPTIONS from ..rfb_utils.shadergraph_utils import find_node, find_selected_pattern_node, is_socket_same_type, find_material_from_nodetree +from ..rfb_utils.shadergraph_utils import set_solo_node import bpy import os @@ -524,60 +525,17 @@ class NODE_OT_rman_node_set_solo(bpy.types.Operator): solo_node_name: StringProperty(default="") refresh_solo: BoolProperty(default=False) - def hide_all(self, nt, node): - if not get_pref('rman_solo_collapse_nodes'): - return - for n in nt.nodes: - hide = (n != node) - if hasattr(n, 'prev_hidden'): - setattr(n, 'prev_hidden', n.hide) - n.hide = hide - for input in n.inputs: - if not input.is_linked: - if hasattr(input, 'prev_hidden'): - setattr(input, 'prev_hidden', input.hide) - input.hide = hide - - for output in n.outputs: - if not output.is_linked: - if hasattr(output, 'prev_hidden'): - setattr(output, 'prev_hidden', output.hide) - output.hide = hide - - def unhide_all(self, nt): - if not get_pref('rman_solo_collapse_nodes'): - return - for n in nt.nodes: - hide = getattr(n, 'prev_hidden', False) - n.hide = hide - for input in n.inputs: - if not input.is_linked: - hide = getattr(input, 'prev_hidden', False) - input.hide = hide - - for output in n.outputs: - if not output.is_linked: - hide = getattr(output, 'prev_hidden', False) - output.hide = hide - def invoke(self, context, event): nt = context.nodetree output_node = context.node selected_node = None if self.refresh_solo: - output_node.solo_nodetree = None - output_node.solo_node_name = '' - output_node.solo_node_output = '' - self.unhide_all(nt) + set_solo_node(output_node, nt, '', refresh_solo=True) return {'FINISHED'} if self.solo_node_name: - output_node.solo_nodetree = nt - output_node.solo_node_name = self.solo_node_name - output_node.solo_node_output = '' - solo_node = nt.nodes[self.solo_node_name] - self.hide_all(nt, solo_node) + set_solo_node(output_node, nt, self.solo_node_name, refresh_solo=False) return {'FINISHED'} selected_node = find_selected_pattern_node(nt) @@ -585,11 +543,8 @@ def invoke(self, context, event): if not selected_node: self.report({'ERROR'}, "Pattern node not selected") return {'FINISHED'} - - output_node.solo_nodetree = nt - output_node.solo_node_name = selected_node.name - output_node.solo_node_output = '' - self.hide_all(nt, nt.nodes[selected_node.name]) + + set_solo_node(output_node, nt, selected_node.name, refresh_solo=False) return {'FINISHED'} diff --git a/rman_bl_nodes/rman_bl_nodes_props.py b/rman_bl_nodes/rman_bl_nodes_props.py index 6b74b61f..b3f188be 100644 --- a/rman_bl_nodes/rman_bl_nodes_props.py +++ b/rman_bl_nodes/rman_bl_nodes_props.py @@ -337,6 +337,12 @@ def update_solo(self, context): description="Lock from changing light shader and light role." ) + linkingGroups: StringProperty( + name="linkingGroups", + default = "", + description="What linking groups this light filter belongs to. These work in conjunction to the 'lightfilter:subset' attrbiute" + ) + # OLD PROPERTIES shadingrate: FloatProperty( diff --git a/rman_bl_nodes/rman_bl_nodes_shaders.py b/rman_bl_nodes/rman_bl_nodes_shaders.py index 81541165..4932ea8f 100644 --- a/rman_bl_nodes/rman_bl_nodes_shaders.py +++ b/rman_bl_nodes/rman_bl_nodes_shaders.py @@ -28,7 +28,7 @@ class RendermanShadingNode(bpy.types.ShaderNode): prev_hidden: BoolProperty(default=False, description="Whether or not this node was previously hidden.") def update_mat(self, mat): - if self.renderman_node_type == 'bxdf' and self.outputs['Bxdf'].is_linked: + if self.renderman_node_type == 'bxdf' and self.outputs['bxdf_out'].is_linked: mat.specular_color = [1, 1, 1] mat.diffuse_color = [1, 1, 1, 1] mat.specular_intensity = 0 @@ -179,7 +179,7 @@ def draw_nonconnectable_prop(self, context, layout, prop_name, output_node=None, nt = node.id_data layout.enabled = (nt.library is None) layout.template_color_ramp( - ramp_node, 'color_ramp') + ramp_node, 'color_ramp') return elif bl_prop_info.widget == 'floatramp': node_group = self.rman_fake_node_group_ptr @@ -197,7 +197,10 @@ def draw_nonconnectable_prop(self, context, layout, prop_name, output_node=None, nt = node.id_data layout.enabled = (nt.library is None) layout.template_curve_mapping( - ramp_node, 'mapping') + ramp_node, 'mapping') + interp_name = '%s_Interpolation' % prop_name + if hasattr(node, interp_name): + layout.prop(node, interp_name, text='Ramp Interpolation') return if prop_name not in node.inputs: @@ -685,13 +688,13 @@ def init(self, context): self._init_inputs() def _init_inputs(self): - input = self.inputs.new('RendermanNodeSocketBxdf', 'Bxdf') + input = self.inputs.new('RendermanNodeSocketBxdf', 'bxdf_in', identifier='Bxdf') input.hide_value = True - input = self.inputs.new('RendermanNodeSocketLight', 'Light') + input = self.inputs.new('RendermanNodeSocketLight', 'light_in', identifier='Light') input.hide_value = True - input = self.inputs.new('RendermanNodeSocketDisplacement', 'Displacement') + input = self.inputs.new('RendermanNodeSocketDisplacement', 'displace_in', identifier='Displacement') input.hide_value = True - input = self.inputs.new('RendermanNodeSocketLightFilter', 'LightFilter') + input = self.inputs.new('RendermanNodeSocketLightFilter', 'lightfilter_in', identifier='LightFilter') input.hide_value = True def draw_buttons(self, context, layout): @@ -726,16 +729,28 @@ def update(self): pass self.new_links.clear() + + # check if the solo node still exists + if self.solo_node_name: + solo_nodetree = self.solo_nodetree + solo_node = solo_nodetree.nodes.get(self.solo_node_name, None) + if solo_node is None: + shadergraph_utils.set_solo_node(self, solo_nodetree, '', refresh_solo=True) + solo_nodetree.update_tag() + return + + self.id_data.update_tag() # This sucks. There doesn't seem to be a way to tag the material # it needs updating, so we manually issue an edit - + ''' area = getattr(bpy.context, 'area', None) if area and area.type == 'NODE_EDITOR': rr = rman_render.RmanRender.get_rman_render() mat = getattr(bpy.context, 'material', None) if mat: rr.rman_scene_sync.update_material(mat) + ''' class RendermanIntegratorsOutputNode(RendermanShadingNode): bl_label = 'RenderMan Integrators' @@ -745,7 +760,7 @@ class RendermanIntegratorsOutputNode(RendermanShadingNode): new_links = [] def init(self, context): - input = self.inputs.new('RendermanNodeSocketIntegrator', 'Integrator') + input = self.inputs.new('RendermanNodeSocketIntegrator', 'integrator_in', identifier='Integrator') def draw_buttons(self, context, layout): return @@ -785,11 +800,12 @@ class RendermanSamplefiltersOutputNode(RendermanShadingNode): new_links = [] def init(self, context): - input = self.inputs.new('RendermanNodeSocketSampleFilter', 'samplefilter[0]') + input = self.inputs.new('RendermanNodeSocketSampleFilter', 'samplefilter_in[0]', identifier='samplefilter[0]') input.hide_value = True def add_input(self): - input = self.inputs.new('RendermanNodeSocketSampleFilter', 'samplefilter[%d]' % (len(self.inputs))) + size = len(self.inputs) + input = self.inputs.new('RendermanNodeSocketSampleFilter', 'samplefilter_in[%d]' % size, identifier='samplefilter[%d]' % size) input.hide_value = True def remove_input(self): @@ -850,11 +866,12 @@ class RendermanDisplayfiltersOutputNode(RendermanShadingNode): new_links = [] def init(self, context): - input = self.inputs.new('RendermanNodeSocketDisplayFilter', 'displayfilter[0]') + input = self.inputs.new('RendermanNodeSocketDisplayFilter', 'displayfilter_in[0]', identifier='displayflter[0]') input.hide_value = True def add_input(self): - input = self.inputs.new('RendermanNodeSocketDisplayFilter', 'displayfilter[%d]' % (len(self.inputs))) + size = len(self.inputs) + input = self.inputs.new('RendermanNodeSocketDisplayFilter', 'displayfilter_in[%d]' % size, identifier='displayfilter[%d]' % size) input.hide_value = True def remove_input(self): @@ -918,7 +935,7 @@ def poll(cls, ntree): return ntree.bl_idname == 'ShaderNodeTree' def init(self, context): - input = self.inputs.new('RendermanNodeSocketProjection', 'Projection') + input = self.inputs.new('RendermanNodeSocketProjection', 'projection_in', identifier='Projection') def draw_buttons(self, context, layout): return diff --git a/rman_bl_nodes/rman_bl_nodes_sockets.py b/rman_bl_nodes/rman_bl_nodes_sockets.py index 81c12354..e09cf2a3 100644 --- a/rman_bl_nodes/rman_bl_nodes_sockets.py +++ b/rman_bl_nodes/rman_bl_nodes_sockets.py @@ -223,10 +223,13 @@ def update_func(self, context): class RendermanSocket: ui_open: BoolProperty(name='UI Open', default=True) + rman_label: StringProperty(name="rman_label", default="") def get_pretty_name(self, node): if node.bl_idname in __CYCLES_GROUP_NODES__: return self.name + elif self.rman_label != '': + return self.rman_label else: return self.identifier @@ -249,6 +252,11 @@ def draw(self, context, layout, node, text): pass elif self.hide_value: layout.label(text=self.get_pretty_name(node)) + elif self.is_output: + if self.is_array: + layout.label(text='%s[]' % (self.get_pretty_name(node))) + else: + layout.label(text=self.get_pretty_name(node)) elif self.is_linked or self.is_output: layout.label(text=self.get_pretty_name(node)) elif node.bl_idname in __CYCLES_GROUP_NODES__ or node.bl_idname == "PxrOSLPatternNode": @@ -348,6 +356,9 @@ def draw_color(self, context, node): ann_dict = socket_info[5] for k, v in ann_dict.items(): ntype.__annotations__[k] = v + ntype.__annotations__['is_array'] = BoolProperty(default=False) + ntype.__annotations__['array_size'] = IntProperty(default=-1) + ntype.__annotations__['array_elem'] = IntProperty(default=-1) classes.append(ntype) diff --git a/rman_config/__init__.py b/rman_config/__init__.py index 8d6223a2..3a2fea9a 100644 --- a/rman_config/__init__.py +++ b/rman_config/__init__.py @@ -73,6 +73,8 @@ def addpage(ndp): for param_name, ndp in config.params.items(): update_func = None + set_func = None + get_func = None if hasattr(ndp, 'update_function'): # this code tries to dynamically add a function to cls # don't ask me why this works. @@ -83,11 +85,30 @@ def addpage(ndp): setattr(cls, ndp.update_function_name, update_func) elif hasattr(ndp, 'update_function_name'): update_func = ndp.update_function_name + + if hasattr(ndp, 'set_function'): + lcls = locals() + exec(ndp.set_function, globals(), lcls) + exec('set_func = %s' % ndp.set_function_name, globals(), lcls) + set_func = lcls['set_func'] + setattr(cls, ndp.set_function_name, set_func) + elif hasattr(ndp, 'set_function_name'): + set_func = ndp.set_function_name + + if hasattr(ndp, 'get_function'): + lcls = locals() + exec(ndp.get_function, globals(), lcls) + exec('get_func = %s' % ndp.get_function_name, globals(), lcls) + get_func = lcls['get_func'] + setattr(cls, ndp.get_function_name, get_func) + elif hasattr(ndp, 'get_function_name'): + get_func = ndp.get_function_name + if ndp.is_array(): if generate_property_utils.generate_array_property(cls, prop_names, prop_meta, ndp): addpage(ndp) continue - name, meta, prop = generate_property_utils.generate_property(cls, ndp, update_function=update_func) + name, meta, prop = generate_property_utils.generate_property(cls, ndp, update_function=update_func, set_function=set_func, get_function=get_func) if prop: cls.__annotations__[ndp._name] = prop prop_names.append(ndp.name) diff --git a/rman_config/config/overrides/rman_properties_PxrCookieLightFilter.json b/rman_config/config/overrides/rman_properties_PxrCookieLightFilter.json new file mode 100644 index 00000000..37e82d6e --- /dev/null +++ b/rman_config/config/overrides/rman_properties_PxrCookieLightFilter.json @@ -0,0 +1,9 @@ +{ + "name": "PxrCookieLightFilter.args", + "params": [ + { + "name": "refreshMap", + "widget": "__REMOVE__" + } + ] +} \ No newline at end of file diff --git a/rman_config/config/overrides/rman_properties_PxrGoboLightFilter.json b/rman_config/config/overrides/rman_properties_PxrGoboLightFilter.json new file mode 100644 index 00000000..9b5009ee --- /dev/null +++ b/rman_config/config/overrides/rman_properties_PxrGoboLightFilter.json @@ -0,0 +1,9 @@ +{ + "name": "PxrGoboLightFilter.args", + "params": [ + { + "name": "refreshMap", + "widget": "__REMOVE__" + } + ] +} \ No newline at end of file diff --git a/rman_config/config/rfb.json b/rman_config/config/rfb.json index bc1897c5..05994223 100644 --- a/rman_config/config/rfb.json +++ b/rman_config/config/rfb.json @@ -1,9 +1,6 @@ { "$schema": "./schemas/rfbSchema.json", "tractor_cfg": { - "engine": "tractor-engine", - "port": 80, - "user": "" }, "dirmaps": { }, @@ -81,7 +78,7 @@ "diffuse_color": ["diffuseColor"] }, "LamaDiffuse": { - "diffuse_color": ["color"] + "diffuse_color": ["diffuseColor"] } }, "disabled_nodes": [ diff --git a/rman_config/config/rman_dspychan_definitions.json b/rman_config/config/rman_dspychan_definitions.json index b00e3d6e..b870e2c0 100644 --- a/rman_config/config/rman_dspychan_definitions.json +++ b/rman_config/config/rman_dspychan_definitions.json @@ -183,6 +183,13 @@ "statistics": "variance", "group": "Denoiser" }, + "albedo_mse": { + "description": "", + "channelType": "color", + "channelSource": "lpe:nothruput;noinfinitecheck;noclamp;unoccluded;overwrite;C<.S'passthru'>*((U2L)|O)", + "statistics": "mse", + "group": "Denoiser" + }, "diffuse": { "description": "", "channelType": "color", @@ -235,6 +242,13 @@ "statistics": "variance", "group": "Denoiser" }, + "normal_mse": { + "description": "", + "channelType": "normal", + "channelSource": "lpe:nothruput;noinfinitecheck;noclamp;unoccluded;overwrite;CU6L", + "statistics": "mse", + "group": "Denoiser" + }, "forward": { "description": "", "channelType": "vector", @@ -723,7 +737,12 @@ "channelType": "color", "channelSource": "NPRtoonDiffRamp", "group": "NPR" - } + }, + "NPRdistort": { + "channelType": "color", + "channelSource": "NPRdistort", + "group": "NPR" + } }, "displays": { "Default": { @@ -739,8 +758,10 @@ "description": "Create a variance file appropriate for the denoiser.", "channels": [ "mse", + "sampleCount", "albedo", "albedo_var", + "albedo_mse", "diffuse", "diffuse_mse", "specular", @@ -749,6 +770,7 @@ "zfiltered_var", "normal", "normal_var", + "normal_mse", "forward", "backward" ] @@ -802,7 +824,8 @@ "NPRalbedo", "NPRtextureCoords", "NPRPtriplanar", - "NPRNtriplanar" + "NPRNtriplanar", + "NPRdistort" ] } } diff --git a/rman_config/config/rman_properties_camera.json b/rman_config/config/rman_properties_camera.json index fc44c16b..08f76d2c 100644 --- a/rman_config/config/rman_properties_camera.json +++ b/rman_config/config/rman_properties_camera.json @@ -6,7 +6,7 @@ "name": "rman_use_dof", "label": "Depth of Field", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "checkbox", "page": "", @@ -16,7 +16,7 @@ "panel": "DATA_PT_renderman_camera", "name": "rman_aperture_fstop", "label": "F Stop", - "editable": true, + "ipr_editable": true, "type": "float", "default": 2.8, "page": "Aperture Controls", @@ -32,7 +32,7 @@ "name": "rman_aperture_ratio", "label": "Ratio", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "Aperture Controls", "help": "" @@ -42,7 +42,7 @@ "name": "rman_aperture_blades", "label": "Blades", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "page": "Aperture Controls", "help": "" @@ -52,7 +52,7 @@ "name": "rman_aperture_rotation", "label": "Rotation", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "min": -180.0, "max": 180.0, @@ -64,7 +64,7 @@ "name": "rman_aperture_roundness", "label": "Roundness", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "min": 0.0, "max": 1.0, @@ -76,7 +76,7 @@ "name": "rman_aperture_density", "label": "Density", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "min": 0.0, "max": 1.0, @@ -89,7 +89,7 @@ "name": "rman_focus_object", "label": "Focus Object", "type": "string", - "editable": true, + "ipr_editable": true, "widget": "bl_scenegraphLocation", "options": "nodeType:bpy.types.Object", "default": "" @@ -100,7 +100,7 @@ "name": "rman_focus_distance", "label": "Distance", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "conditionalVisOps": { "conditionalVisOp": "equalTo", @@ -115,7 +115,7 @@ "name": "rman_use_shutteropening", "label": "Control Shutter Opening", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "checkbox", "page": "Shutter", @@ -126,7 +126,7 @@ "name": "rman_shutteropening_c1", "label": "C1", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "Shutter", "help": "", @@ -141,7 +141,7 @@ "name": "rman_shutteropening_c2", "label": "C2", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "Shutter", "help": "", @@ -156,7 +156,7 @@ "name": "rman_shutteropening_d1", "label": "D1", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "Shutter", "help": "", @@ -171,7 +171,7 @@ "name": "rman_shutteropening_d2", "label": "D2", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "Shutter", "help": "", @@ -186,7 +186,7 @@ "name": "rman_shutteropening_e1", "label": "E1", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "Shutter", "help": "", @@ -201,7 +201,7 @@ "name": "rman_shutteropening_e2", "label": "E2", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "Shutter", "help": "", @@ -216,7 +216,7 @@ "name": "rman_shutteropening_f1", "label": "F1", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "Shutter", "help": "", @@ -231,7 +231,7 @@ "name": "rman_shutteropening_f2", "label": "F2", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "Shutter", "help": "", @@ -246,7 +246,7 @@ "name": "rman_stereoplanedepths", "label": "Stereo Plane Depths", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "size": -1, "page": "Stereo Planes", @@ -257,7 +257,7 @@ "name": "rman_stereoplaneoffsets", "label": "Stereo Plane Offsets", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "size": -1, "page": "Stereo Planes", diff --git a/rman_config/config/rman_properties_curve.json b/rman_config/config/rman_properties_curve.json index 4cd01905..851eed14 100644 --- a/rman_config/config/rman_properties_curve.json +++ b/rman_config/config/rman_properties_curve.json @@ -7,7 +7,7 @@ "primvar": "polygon:smoothnormals", "label": "Smooth Normals", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "checkBox", "page": "Polygons", @@ -19,7 +19,7 @@ "primvar": "polygon:smoothdisplacement", "label": "Prevent polygon cracking", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "checkBox", "page": "Polygons", @@ -30,7 +30,7 @@ "name": "rman_subdiv_scheme", "label": "Scheme", "type": "string", - "editable": true, + "ipr_editable": true, "default": "none", "widget": "mapper", "options": "None:none|Catmull-Clark:catmull-clark|Loop:loop|Bilinear:bilinear", @@ -42,7 +42,7 @@ "primvar": "dice:watertight", "label": "Water-tight dicing", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "mapper", "options": "Yes:1|No:0", @@ -59,7 +59,7 @@ "name": "rman_subdivInterp", "label": "Interpolation", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "mapper", "options": "No interpolation:0|Sharp creases and corners:1|Sharp creases:2", @@ -75,7 +75,7 @@ "name": "rman_subdivFacevaryingInterp", "label": "Face-Varying Interpolation", "type": "int", - "editable": true, + "ipr_editable": true, "default": 3, "widget": "mapper", "options": "Old Style:0|New Style:1|New Style, no corners:2|New Style, internal only:3", @@ -91,18 +91,34 @@ "name": "export_default_uv", "label": "Export Default UV", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", "help": "Export the active UV set as the default 'st' primitive variable", "page": "" }, + { + "panel": "MESH_PT_renderman_prim_vars", + "name": "export_default_tangents", + "label": "Export Default Tangents", + "type": "int", + "ipr_editable": true, + "default": 0, + "widget": "checkbox", + "help": "Export the tangent and bitangent vectors for the active UV set. They will be exported as 'Tn' and 'Bn' primitive variables, respectively", + "page": "", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "export_default_uv", + "conditionalVisValue": "0" + } + }, { "panel": "CURVE_PT_renderman_prim_vars", "name": "export_default_vcol", "label": "Export Default Vertex Color", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", "help": "Export the active Vertex Color set as the default 'Cs' primitive variable", @@ -114,7 +130,7 @@ "primvar": "curve:widthaffectscurvature", "label": "Width Affects Curvature", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", "help": "When true the curve width of round curves is taken into account in the computation of the tube curvature, otherwise only the curvature along the curve is. This control does not affect curve ribbons.", diff --git a/rman_config/config/rman_properties_dspychan.json b/rman_config/config/rman_properties_dspychan.json index 582927d2..2ba42e09 100644 --- a/rman_config/config/rman_properties_dspychan.json +++ b/rman_config/config/rman_properties_dspychan.json @@ -42,7 +42,7 @@ { "panel": "RENDER_PT_layer_custom_aovs", "name": "remap_a", - "label": "a", + "label": "Break Point", "type": "float", "default": 0.0, "page": "Remap Settings", @@ -56,7 +56,7 @@ { "panel": "RENDER_PT_layer_custom_aovs", "name": "remap_b", - "label": "b", + "label": "Max Value", "type": "float", "default": 0.0, "page": "Remap Settings", @@ -70,7 +70,7 @@ { "panel": "RENDER_PT_layer_custom_aovs", "name": "remap_c", - "label": "c", + "label": "Smoothness", "type": "float", "default": 0.0, "page": "Remap Settings", @@ -86,11 +86,11 @@ "name": "chan_pixelfilter", "label": "Pixel Filter", "type": "string", - "default": "box", + "default": "default", "page": "Pixel Filter", - "widget": "mapper", - "options": "Default:default|Box:box|Sinc:sinc|Gaussian:gaussian|Triangle:triangle|Catmull-Rom:catmull-rom", - "help": "", + "widget": "popup", + "options": "default|box|triangle|catmull-rom|sinc|gaussian|mitchell|separable-catmull-rom|blackman-harris|lanczos|bessel|disk|min|max|average|zmin|zmax|sum", + "help": "The name of the pixel filter to be used for the output display. In addition, five special filters may be used: min, max, average, zmin, and zmax. The first three filters have the same meaning as the depthfilter argument to Hider, i.e. instead of running a convolution filter across all samples, only a single value (the minimum, maximum, or average of all pixel samples) is returned and written into the final pixel value. The zmin and zmax filters operate like the min and max filters, except that the depth value of the pixel sample is used for comparison, and not the value implied by the mode itself. These filters are useful for arbitrary output variables where standard alpha compositing does not make sense, or where linear interpolation of values between disjoint pieces of geometry is nonsensical. Note that when these filters are used, opacity thresholding is also used on that output to determine which closest surface to sample.", "conditionalVisOps": { "conditionalVisOp": "equalTo", "conditionalVisPath": "show_advanced", @@ -138,7 +138,7 @@ "page": "Statistics", "widget": "mapper", "options": "None:none|Variance:variance|MSE:mse|Even:even|Odd:odd", - "help": "", + "help": "Default to empty. Indicates that this display channel should compute statistical measures on another display channel (specified via the source parameter). The statistics channel must have matching filter settings. The options available are: 'variance' to estimate the variance of the samples in each pixel, 'mse' which is the estimate of the variance divided by the actual number of samples per pixel, 'even' for an image created from half the total camera samples, and 'odd' for an image from the other half of the camera samples.", "conditionalVisOps": { "conditionalVisOp": "equalTo", "conditionalVisPath": "show_advanced", diff --git a/rman_config/config/rman_properties_mesh.json b/rman_config/config/rman_properties_mesh.json index 2ef4321f..a1fcd8c6 100644 --- a/rman_config/config/rman_properties_mesh.json +++ b/rman_config/config/rman_properties_mesh.json @@ -7,10 +7,11 @@ "primvar": "polygon:smoothnormals", "label": "Smooth Normals", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "checkBox", "page": "Polygons", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_smoothnormals', c)", "help": "Render polygons with smooth normals." }, { @@ -19,10 +20,11 @@ "primvar": "polygon:smoothdisplacement", "label": "Prevent polygon cracking", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "checkBox", "page": "Polygons", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_preventPolyCracks', c)", "help": "Prevent polygon cracks on sharp edges, as long as both sides of the edge have similar displacement values, at the cost of slightly warping your displacement." }, { @@ -30,7 +32,7 @@ "name": "rman_subdiv_scheme", "label": "Scheme", "type": "string", - "editable": true, + "ipr_editable": true, "default": "none", "widget": "mapper", "options": "None:none|Catmull-Clark:catmull-clark|Loop:loop|Bilinear:bilinear", @@ -42,11 +44,12 @@ "primvar": "dice:watertight", "label": "Water-tight dicing", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "mapper", "options": "Yes:1|No:0", "page": "Subdivision Mesh", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_watertight', c)", "help": "Fix pin-holes in Subdivision Surfaces.
It may slow down dicing and time to first pixel.", "conditionalVisOps": { "conditionalVisOp": "notEqualTo", @@ -59,7 +62,7 @@ "name": "rman_subdivInterp", "label": "Interpolation", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "mapper", "options": "No interpolation:0|Sharp creases and corners:1|Sharp creases:2", @@ -75,7 +78,7 @@ "name": "rman_subdivFacevaryingInterp", "label": "Face-Varying Interpolation", "type": "int", - "editable": true, + "ipr_editable": true, "default": 3, "widget": "mapper", "options": "Old Style:0|New Style:1|New Style, no corners:2|New Style, internal only:3", @@ -86,23 +89,56 @@ "conditionalVisValue": "none" } }, + { + "panel": "MESH_PT_renderman_mesh_attrs", + "name": "rman_holesFaceMap", + "label": "Holes Face Map", + "type": "string", + "default": "", + "page": "Subdivision Mesh", + "ipr_editable": true, + "widget": "propSearch", + "options": "prop_parent:context.object|prop_name:face_maps", + "help": "You can select a face map to act as holes for your subdiv.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "rman_subdiv_scheme", + "conditionalVisValue": "none" + } + }, { "panel": "MESH_PT_renderman_prim_vars", "name": "export_default_uv", "label": "Export Default UV", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", "help": "Export the active UV set as the default 'st' primitive variable", "page": "" }, + { + "panel": "MESH_PT_renderman_prim_vars", + "name": "export_default_tangents", + "label": "Export Default Tangents", + "type": "int", + "ipr_editable": true, + "default": 0, + "widget": "checkbox", + "help": "Export the tangent and bitangent vectors for the active UV set. They will be exported as 'Tn' and 'Bn' primitive variables, respectively", + "page": "", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "export_default_uv", + "conditionalVisValue": "0" + } + }, { "panel": "MESH_PT_renderman_prim_vars", "name": "export_default_vcol", "label": "Export Default Vertex Color", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", "help": "Export the active Vertex Color set as the default 'Cs' primitive variable", diff --git a/rman_config/config/rman_properties_object.json b/rman_config/config/rman_properties_object.json index a92673d8..3ae30885 100644 --- a/rman_config/config/rman_properties_object.json +++ b/rman_config/config/rman_properties_object.json @@ -94,7 +94,7 @@ "name": "quadric_cone_height", "label": "Height", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "", "help": "Height offset above XY plane", @@ -109,7 +109,7 @@ "name": "quadric_disk_height", "label": "Height", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "", "help": "Height offset above XY plane", @@ -124,7 +124,7 @@ "name": "quadric_radius", "label": "Radius", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "", "conditionalVisOps": { @@ -138,7 +138,7 @@ "name": "quadric_majorradius", "label": "Major Radius", "type": "float", - "editable": true, + "ipr_editable": true, "default": 2.0, "page": "", "conditionalVisOps": { @@ -152,7 +152,7 @@ "name": "quadric_majorradius", "label": "Major Radius", "type": "float", - "editable": true, + "ipr_editable": true, "default": 2.0, "page": "", "help": "Radius of Torus ring", @@ -167,7 +167,7 @@ "name": "quadric_minorradius", "label": "Minor Radius", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.5, "page": "", "help": "Radius of Torus cross-section circle", @@ -182,7 +182,7 @@ "name": "quadric_phimin", "label": "Minimum Cross-section", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "", "help": "Minimum angle of cross-section circle", @@ -197,7 +197,7 @@ "name": "quadric_phimax", "label": "Maximum Cross-section", "type": "float", - "editable": true, + "ipr_editable": true, "default": 360.0, "page": "", "help": "Maximum angle of cross-section circle", @@ -212,7 +212,7 @@ "name": "quadric_zmin", "label": "Z min", "type": "float", - "editable": true, + "ipr_editable": true, "default": -1.0, "page": "", "help": "Minimum height clipping of the primitive", @@ -227,7 +227,7 @@ "name": "quadric_zmax", "label": "Z max", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "", "help": "Maximum height clipping of the primitive", @@ -242,7 +242,7 @@ "name": "quadric_sweepangle", "label": "Sweep Angle", "type": "float", - "editable": true, + "ipr_editable": true, "default": 360.0, "page": "", "help": "Angle of clipping around the Z axis", @@ -258,7 +258,7 @@ "name": "runprogram_path", "label": "RunProgram Path", "type": "string", - "editable": true, + "ipr_editable": true, "default": "", "page": "", "widget": "fileinput", @@ -269,7 +269,7 @@ "name": "runprogram_args", "label": "RunProgram Arguments", "type": "string", - "editable": true, + "ipr_editable": true, "default": "", "page": "", "help": "Command line arguments to RunProgram" @@ -280,7 +280,7 @@ "name": "path_dso", "label": "DSO Path", "type": "string", - "editable": true, + "ipr_editable": true, "default": "", "page": "", "widget": "fileinput", @@ -291,7 +291,7 @@ "name": "path_dso_initial_data", "label": "DSO Initial Data", "type": "string", - "editable": true, + "ipr_editable": true, "default": "", "page": "", "help": "Parameters to send the DSO" @@ -302,7 +302,7 @@ "name": "path_archive", "label": "Archive Path", "type": "string", - "editable": true, + "ipr_editable": true, "default": "", "page": "", "widget": "fileinput", @@ -323,7 +323,7 @@ "name": "abc_filepath", "label": "File Path", "type": "string", - "editable": true, + "ipr_editable": true, "default": "", "page": "", "widget": "fileinput", @@ -334,7 +334,7 @@ "name": "abc_use_scene_frame", "label": "Use Scene Frame", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "", "widget": "checkbox", @@ -345,7 +345,7 @@ "name": "abc_frame", "label": "Frame", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "", "help": "Frame", @@ -360,7 +360,7 @@ "name": "abc_fps", "label": "FPS", "type": "float", - "editable": true, + "ipr_editable": true, "default": 24.0, "page": "", "help": "Frames per second" @@ -370,7 +370,7 @@ "name": "abc_velocityScale", "label": "Velocity Scale", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "", "help": "Scale the velocity primvar used to motion blur primitives with time-varying topology. Restart the renderer to see edits." @@ -380,7 +380,7 @@ "name": "primitive_point_width", "label": "Point Width", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.1, "page": "", "help": "Point Width" @@ -395,9 +395,10 @@ "widget": "mapper", "options": "Inherit:-1|Yes:1|No:0", "default": -1, - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_matteObject', c)", "page": "Shading", "help": "This object is rendered as a black hole" }, @@ -410,9 +411,10 @@ "widget": "mapper", "options": "Inherit:-1|Yes:1|No:0", "default": -1, - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_holdout', c)", "page": "Shading", "help": "Useful in holdout workflow. These objects collect reflections, shadows, and transmission." }, @@ -423,9 +425,10 @@ "label": "Index of Refraction", "type": "float", "default": -1, - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_indexofrefraction', c)", "page": "Shading", "help": "Override material IOR for nested dielectrics." }, @@ -435,8 +438,9 @@ "riattr": "shade:minsamples", "label": "Min Samples", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_minsamples', c)", "page": "Shading", "help": "Min Pixel Samples. For fine-tuning adaptive sampling." }, @@ -446,8 +450,9 @@ "riattr": "shade:relativepixelvariance", "label": "Relative Pixel Variance", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_relativepixelvariance', c)", "page": "Shading", "help": "PixelVariance multiplier for camera visible objects. For fine-tuning adaptive sampling." }, @@ -460,7 +465,8 @@ "default": -1, "inheritable": true, "inherit_true_value": -1, - "editable": true, + "ipr_editable": true, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_maxDiffuseDepth', c)", "page": "Trace", "help": "Maximum diffuse light bounces." }, @@ -470,10 +476,11 @@ "riattr": "trace:maxspeculardepth", "label": "Max Specular Depth", "type": "int", - "editable": true, + "ipr_editable": true, "default": -1, "inheritable": true, "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_maxSpecularDepth', c)", "page": "Trace", "help": "Maximum specular light bounces" }, @@ -485,8 +492,9 @@ "type": "string", "default": "", "page": "Trace", - "editable": true, + "ipr_editable": true, "widget": "propSearch", + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_reflectExcludeSubset', c)", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "help": "Exclude object groups from reflections" }, @@ -498,8 +506,9 @@ "type": "string", "default": "", "page": "Trace", - "editable": true, + "ipr_editable": true, "widget": "propSearch", + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_reflectSubset', c)", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "help": "Object groups visible to reflections" }, @@ -511,8 +520,9 @@ "type": "string", "default": "", "page": "Trace", - "editable": true, + "ipr_editable": true, "widget": "propSearch", + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_shadowExcludeSubset', c)", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "help": "Exclude object groups from casting shadows" }, @@ -524,8 +534,9 @@ "type": "string", "default": "", "page": "Trace", - "editable": true, + "ipr_editable": true, "widget": "propSearch", + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_shadowSubset', c)", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "help": "Object groups active in shadows" }, @@ -537,8 +548,9 @@ "type": "string", "default": "", "page": "Trace", - "editable": true, + "ipr_editable": true, "widget": "propSearch", + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_transmitExcludeSubset', c)", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "help": "Exclude object groups from transmission/refractions" }, @@ -550,21 +562,47 @@ "type": "string", "default": "", "page": "Trace", - "editable": true, + "ipr_editable": true, "widget": "propSearch", + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_transmitSubset', c)", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "help": "Object groups visible to transmission/refractions" }, + { + "panel": "", + "name": "rman_lighting_excludesubset_string", + "riattr": "lighting:excludesubset", + "label": "Lighting Exclude Subset", + "type": "string", + "default": "", + "page": "", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_lighting_excludesubset_string', c)", + "help": "Lighting exclude subset" + }, + { + "panel": "", + "name": "rman_lightfilter_subset_string", + "riattr": "lightfilter:subset", + "label": "Light Filter Subset", + "type": "string", + "default": "", + "page": "", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_lightfilter_subset_string', c)", + "help": "Light filter subset" + }, { "panel": "OBJECT_PT_renderman_object_raytracing", "name": "rman_intersectPriority", "riattr": "trace:intersectpriority", "label": "Intersection Priority", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "min": 0, "max": 31, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_intersectPriority', c)", "page": "Nested Dielectrics", "help": "Raytrace intersection priority for nested dielectrics" }, @@ -574,7 +612,7 @@ "primvar": "trace:displacements", "label": "Trace Displacement", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkBox", "page": "" @@ -587,7 +625,7 @@ "type": "int", "default": -1, "widget": "mapper", - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1, "options": "Inherit:-1|Yes:1|No:0", @@ -599,7 +637,7 @@ "primvar": "trace:bias", "label": "Trace Bias", "type": "float", - "editable": true, + "ipr_editable": true, "default": -1, "inheritable": true, "inherit_true_value": -1, @@ -618,7 +656,7 @@ "type": "int", "default": -1, "widget": "mapper", - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1, "page": "Bias", @@ -632,7 +670,7 @@ "label": "Trace Bias", "type": "float", "default": -1, - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1, "page": "Bias", @@ -649,8 +687,10 @@ "primvar": "displacementbound:sphere", "label": "Displacement Bound", "type": "float", - "editable": true, + "ipr_editable": true, + "always_write": true, "default": 0.1, + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_displacementBound', c)", "page": "Displacement" }, { @@ -660,10 +700,12 @@ "label": "Coordinate System", "type": "string", "default": "object", - "editable": true, + "ipr_editable": true, + "always_write": true, "widget": "popup", "options": "world|object", "page": "Displacement", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_displacementCoordSys', c)", "help": "Coordinate system of the displacement bound. For example, a scaled up ground plane may want the displacement bound in world space, while a character would typically specify the value in object space." }, { @@ -673,33 +715,47 @@ "label": "MicroPolygon Length", "type": "float", "default": -1.0, - "editable": true, + "ipr_editable": true, "inheritable": true, "inherit_true_value": -1.0, "page": "Dicing", "help": "Micropolygon distance in raster space for 'instanceprojection' dicing. Values are expressed in pixel size.", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_micropolygonlength', c)", "conditionalVisOps": { - "conditionalVisOp": "notEqualTo", - "conditionalVisPath": "bl_object_type", - "conditionalVisValue": "OPENVDB" - } + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "notEqualTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "and", + "conditionalVis1Op": "notEqualTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + } }, { "panel": "OBJECT_PT_renderman_object_geometry_attributes", "name": "rman_micropolygonlength_volume", + "primvar": "dice:micropolygonlength", "label": "MicroPolygon Length", "type": "float", "default": 10.0, - "editable": true, - "inheritable": true, - "inherit_true_value": -1.0, + "ipr_editable": true, + "always_write": true, "page": "Dicing", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_micropolygonlength_volume', c)", "help": "Micropolygon distance in raster space for 'instanceprojection' dicing. Values are expressed in pixel size.", "conditionalVisOps": { - "conditionalVisOp": "equalTo", - "conditionalVisPath": "bl_object_type", - "conditionalVisValue": "OPENVDB" - } + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "or", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + } }, { "panel": "OBJECT_PT_renderman_object_geometry_attributes", @@ -707,10 +763,11 @@ "name": "rman_diceStrategy", "primvar": "dice:strategy", "type": "string", - "editable": true, + "ipr_editable": true, "label": "Dicing Strategy", "default": "instanceprojection", "widget": "popup", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_diceStrategy', c)", "options": "instanceprojection|worlddistance|objectdistance", "help": "Dicing method of objects within the viewing frustum.
instanceprojection: Use object size in screen space.
worlddistance: Use object size in world space
objectdistance: Use object size in object space." }, @@ -721,7 +778,8 @@ "primvar": "dice:minlength", "label": "Min Length", "type": "float", - "editable": true, + "ipr_editable": true, + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_minlength', c)", "default": 1 }, { @@ -733,7 +791,8 @@ "type": "string", "default": "world", "widget": "popup", - "editable": true, + "ipr_editable": true, + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_minlengthspace', c)", "options": "world|object|camera" }, @@ -743,10 +802,11 @@ "primvar": "dice:rasterorient", "label": "Raster-Oriented Dicing", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", "page": "Dicing", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_rasterorient', c)", "help": "Changes micropolygon size based on the viewing angle of an object.
When rasterorient is on, surfaces are diced more coarsely at a glancing angle.
It may be useful to turn off for instances as their geometry is seen from different angles
or for objects where displacement details are lost.", "conditionalVisOps": { "conditionalVisOp": "equalTo", @@ -759,8 +819,9 @@ "page": "Dicing", "name": "rman_diceDistanceLength", "primvar": "dice:worlddistancelength", + "update_function_name": "lambda s, c: update_primvar_func(s, 'rman_diceDistanceLength', c)", "type": "float", - "editable": true, + "ipr_editable": true, "label": "Dicing Distance Length", "default": -1.0, "min": -1, @@ -778,7 +839,7 @@ "name": "dice_referenceCameraType", "label": "Dice Camera", "type": "int", - "editable": true, + "ipr_editable": true, "default": 0, "widget": "mapper", "options": "Inherit from Globals:0|Reference Camera:1", @@ -791,16 +852,133 @@ "label": "Reference Camera", "primvar": "dice:referencecamera", "type": "string", - "editable": true, + "ipr_editable": true, "widget": "bl_scenegraphLocation", "options": "nodeType:bpy.types.Camera", + "update_function_name": "lambda s, c: update_primvar_func(s, 'dice_referenceCamera', c)", "default": "", "conditionalVisOps": { "conditionalVisOp": "equalTo", "conditionalVisPath": "dice_referenceCameraType", "conditionalVisValue": 1 } - }, + }, + { + "panel": "OBJECT_PT_renderman_object_geometry_volume", + "page": "", + "name": "volume_temporalmethod", + "label": "Temporal Method", + "primvar": "volume:temporalmethod", + "type": "int", + "ipr_editable": true, + "default": 0, + "widget": "mapper", + "options": "Eulerian:0|Reves:1", + "conditionalVisOps": { + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "or", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + }, + "update_function_name": "lambda s, c: update_primvar_func(s, 'volume_temporalmethod', c)", + "help": "Method of generating temporal data for volume rendering." + }, + { + "panel": "OBJECT_PT_renderman_object_geometry_volume", + "page": "", + "name": "volume_fps", + "label": "Velocity frames per second", + "primvar": "volume:fps", + "type": "float", + "ipr_editable": true, + "default": 1, + "conditionalVisOps": { + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "or", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + }, + "update_function_name": "lambda s, c: update_primvar_func(s, 'volume_fps', c)", + "help": "The frames per second for volumetric velocity data. The velocity data will be divided by this quantity to derive the velocity for the frame." + }, + { + "panel": "OBJECT_PT_renderman_object_geometry_volume", + "page": "", + "name": "volume_shutteroffset", + "label": "Shutter Offset", + "primvar": "volume:shutteroffset", + "type": "float", + "ipr_editable": true, + "default": 1, + "conditionalVisOps": { + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "or", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + }, + "update_function_name": "lambda s, c: update_primvar_func(s, 'volume_shutteroffset', c)", + "help": "The shutter offset used to interpret volumetric velocity. A value of 1 will use the current position of the object and the position of the object on the next frame as the time interval to use for motion blur. A value of -1 will use the position of the object on the previous frame and the current position of the object as the time. A value of 0 will generate an interval which begins halfway through the previous frame and ends halfway into the next frame." + }, + { + "panel": "OBJECT_PT_renderman_object_geometry_volume", + "page": "", + "name": "volume_velocityshuttercorrection", + "label": "Velocity Shutter Correction", + "primvar": "volume:velocityshuttercorrection", + "type": "int", + "ipr_editable": true, + "default": 0, + "conditionalVisOps": { + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "or", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + }, + "update_function_name": "lambda s, c: update_primvar_func(s, 'volume_velocityshuttercorrection', c)", + "help": "The shutter offset used to interpret volumetric velocity. A value of 1 will use the current position of the object and the position of the object on the next frame as the time interval to use for motion blur. A value of -1 will use the position of the object on the previous frame and the current position of the object as the time. A value of 0 will generate an interval which begins halfway through the previous frame and ends halfway into the next frame." + }, + { + "panel": "OBJECT_PT_renderman_object_geometry_volume", + "page": "", + "name": "volume_global_aggregate", + "label": "Global Volume Aggregate", + "type": "int", + "default": 1, + "widget": "checkbox", + "ipr_editable": true, + "conditionalVisOps": { + "conditionalVis1Path": "bl_object_type", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "OPENVDB", + "conditionalVisOp": "or", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "bl_object_type", + "conditionalVis2Value": "RI_VOLUME" + }, + "help": "Include this volume in the global volume aggregate, if it's not part of an aggregate already. Note, for XPU, volumes have to be in an aggregate in order for them to render." + }, { "panel": "OBJECT_PT_renderman_object_render", "name": "rman_visibilityCamera", @@ -809,10 +987,11 @@ "type": "int", "default": -1, "widget": "mapper", - "editable": true, + "ipr_editable": true, "options": "Inherit:-1|Yes:1|No:0", "inheritable": true, - "inherit_true_value": -1, + "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_visibilityCamera', c)", "page": "Visibility", "help": "Indicates if object is visible to camera rays" }, @@ -824,10 +1003,11 @@ "type": "int", "default": -1, "widget": "mapper", - "editable": true, + "ipr_editable": true, "options": "Inherit:-1|Yes:1|No:0", "inheritable": true, - "inherit_true_value": -1, + "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_visibilityIndirect', c)", "page": "Visibility", "help": "Indicates if object is visible to indirect (reflection and specular refraction) rays" }, @@ -839,10 +1019,11 @@ "type": "int", "default": -1, "widget": "mapper", - "editable": true, + "ipr_editable": true, "options": "Inherit:-1|Yes:1|No:0", "inheritable": true, - "inherit_true_value": -1, + "inherit_true_value": -1, + "update_function_name": "lambda s, c: update_riattr_func(s, 'rman_visibilityTransmission', c)", "page": "Visibility", "help": "Indicates if object is visible to shadow rays" }, @@ -851,13 +1032,14 @@ "name": "user_MatteID0", "label": "Matte ID 0", "riattr": "user:MatteID0", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID0', c)", "help": "Matte ID 0 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -865,13 +1047,14 @@ "name": "user_MatteID1", "label": "Matte ID 1", "riattr": "user:MatteID1", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID1', c)", "help": "Matte ID 1 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -879,13 +1062,14 @@ "name": "user_MatteID2", "label": "Matte ID 2", "riattr": "user:MatteID2", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID2', c)", "help": "Matte ID 2 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -893,13 +1077,14 @@ "name": "user_MatteID3", "label": "Matte ID 3", "riattr": "user:MatteID3", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID3', c)", "help": "Matte ID 2 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -907,13 +1092,14 @@ "name": "user_MatteID4", "label": "Matte ID 4", "riattr": "user:MatteID4", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID4', c)", "help": "Matte ID 4 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -921,13 +1107,14 @@ "name": "user_MatteID5", "label": "Matte ID 5", "riattr": "user:MatteID5", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID5', c)", "help": "Matte ID 5 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -935,13 +1122,14 @@ "name": "user_MatteID6", "label": "Matte ID 6", "riattr": "user:MatteID6", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID6', c)", "help": "Matte ID 6 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -949,13 +1137,14 @@ "name": "user_MatteID7", "label": "Matte ID 7", "riattr": "user:MatteID7", - "editable": true, + "ipr_editable": true, "type": "color", "default": [ 0, 0, 0 ], + "update_function_name": "lambda s, c: update_riattr_func(s, 'user_MatteID7', c)", "help": "Matte ID 7 Color, you also need to add the PxrMatteID node to your bxdf" }, { @@ -975,7 +1164,12 @@ "type": "string", "default": "", "widget": "default", - "help": "Active udims for Integrator baking of texture atlases. Lists and ranges of udims can be provided in the string, for example: '1001,1003,1005-1006'. The default is an empty string which means all udims get baked." + "help": "Active udims for Integrator baking of texture atlases. Lists and ranges of udims can be provided in the string, for example: '1001,1003,1005-1006'. The default is an empty string which means all udims get baked.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "rman_bake_mode", + "conditionalVisValue": "pattern" + } }, { "panel": "OBJECT_PT_renderman_object_baking", @@ -985,7 +1179,12 @@ "type": "string", "default": "", "widget": "default", - "help": "Udim texure resolutions for Integrator baking of texture atlases. Lists and ranges of udims can be provided in the string, for example: '1001 512 512 1002,1003 64 32 1005-1006 16 16'. If the string is empty (the default) then the Display Format resolution will be used." + "help": "Udim texure resolutions for Integrator baking of texture atlases. Lists and ranges of udims can be provided in the string, for example: '1001 512 512 1002,1003 64 32 1005-1006 16 16'. If the string is empty (the default) then the Display Format resolution will be used.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "rman_bake_mode", + "conditionalVisValue": "pattern" + } } ] } \ No newline at end of file diff --git a/rman_config/config/rman_properties_scene.json b/rman_config/config/rman_properties_scene.json index 449ae4b6..f7c5a75b 100644 --- a/rman_config/config/rman_properties_scene.json +++ b/rman_config/config/rman_properties_scene.json @@ -63,7 +63,7 @@ "type": "int", "default": 0, "widget": "checkbox", - "help": "Denoise with OptiX denoiser", + "help": "Denoise Blender render displays with OptiX denoiser", "update_function_name": "update_blender_optix_denoiser", "update_function": "def update_blender_optix_denoiser(self, context):\n from ..rman_render import RmanRender\n rr = RmanRender.get_rman_render()\n rr.rman_scene_sync.update_displays(context)", "conditionalVisOps": { @@ -79,7 +79,7 @@ } }, { - "panel": "RENDER_PT_renderman_render", + "panel": "", "page": "Display", "name": "render_ipr_into", "label": "Render IPR to", @@ -90,14 +90,14 @@ "help": "Controls where IPR renders will go to." }, { - "panel": "RENDER_PT_renderman_render", + "panel": "", "page": "Display", "name": "blender_ipr_optix_denoiser", "label": "Use Optix Denoiser", "type": "int", "default": 0, "widget": "checkbox", - "help": "Denoise IPR with OptiX denoiser", + "help": "Denoise viewport renders with OptiX denoiser", "update_function_name": "update_blender_ipr_optix_denoiser", "update_function": "def update_blender_ipr_optix_denoiser(self, context):\n from ..rman_render import RmanRender\n rr = RmanRender.get_rman_render()\n rr.rman_scene_sync.update_displays(context)", "conditionalVisOps": { @@ -144,8 +144,20 @@ "widget": "mapper", "options": "Off:OFF|In Alpha:ALPHA|Separate AOV:AOV", "help": "Render a holdout matte." - - }, + }, + { + "panel": "RENDER_PT_renderman_render", + "page": "", + "name": "do_persistent_data", + "label": "Persistent Data", + "type": "int", + "default": 0, + "widget": "checkbox", + "help": "Keep render data around for faster animation renders, at the cost of increased memory usage. May not work in all cases.", + "ipr_editable": false, + "update_function_name": "update_do_persistent_data", + "update_function": "def update_do_persistent_data(self, context):\n scene = context.scene\n scene.render.use_persistent_data = self.do_persistent_data" + }, { "panel": "RENDER_PT_renderman_sampling", "page": "IPR Sampling", @@ -159,8 +171,8 @@ "widget": false, "slidermin": 0, "slidermax": 4096, - "editable": true, - "update_function_name": "update_options_func", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_options_func(s, 'ipr_hider_minSamples', c)", "help": "When set to zero this value is automatically computed as the square root of the max samples." }, { @@ -181,8 +193,8 @@ "Production": 1024, "High Quality": 2048 }, - "editable": true, - "update_function_name": "update_options_func", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_options_func(s, 'ipr_hider_maxSamples', c)", "help": "The maximum number of camera rays to be traced for each pixel. When adaptive sampling is enabled (ie. Pixel Variance is greater than zero), fewer rays may be traced in smoother regions of the image." }, { @@ -202,8 +214,8 @@ "Denoising": 0.05, "High Quality": 0.01 }, - "editable": true, - "update_function_name": "update_options_func", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_options_func(s, 'ipr_ri_pixelVariance', c)", "help": "This value is applied during IPR. Adaptive sampling is done when Pixel Variance is greater than zero. Reducing this value increases the likelihood that more rays will be traced while increasing its value allows undersampling." }, { @@ -217,8 +229,8 @@ "min": 0, "max": 6, "widget": false, - "editable": true, - "update_function_name": "update_options_func", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_options_func(s, 'hider_decidither', c)", "help": "This value is only applied during IPR. The value determines how much refinement (in a dither pattern) will be applied to the image during interactive rendering. 0 means full refinement up to a value of 6 which is the least refinement per iteration." }, { @@ -233,7 +245,7 @@ "widget": "mapper", "options": "100%:1.0|50%:0.5|33%:0.33|25%:0.25|12.5%:0.125", "help": "This value is only applied during IPR. This scales down the resolution during viewport renders.", - "editable": true, + "ipr_editable": true, "update_function_name": "update_viewport_res_mult", "update_function": "def update_viewport_res_mult(self, context):\n from ..rman_render import RmanRender\n rr = RmanRender.get_rman_render()\n rr.rman_scene_sync.update_viewport_res_mult(context)", "conditionalVisOps": { @@ -545,7 +557,25 @@ ], "widget": false, "help": "Intended exposure Bracket [min max] in post-production to help inform the adaptive sampling." - }, + }, + { + "panel": "RENDER_PT_renderman_sampling", + "name": "hider_sampleoffset", + "riopt": "hider:sampleoffset", + "label": "Sample Offset", + "type": "int", + "default": 0, + "help": "This allows several images to be rendered in parallel (with different sampleoffset values) and then combined.

With non-adaptive sampling:
Let's say you render four images with 256 samples each, with sampleoffsets 0, 256, 512, and 768. If you combine those four images, you'll get exactly the same image as if you had rendered a single image with 1024 samples.

With adaptive sampling:
Let's say you again render four images, each with 'maxsamples' 256, with sampleoffsets 0, 256, 512, and 768. Let's say that due to adaptive sampling, some given pixel only gets 64 pixel samples in each of the four images. Then the combined image has been rendered with sample numbers 0-63, 256-319, 512-575, and 768-831. Due to the stratification of the samples, this is not quite as good as if you had rendered a single image with 256 consecutive samples. However, it is still better than rendering a single image with only 64 samples." + }, + { + "panel": "RENDER_PT_renderman_sampling", + "name": "hider_samplestride", + "riopt": "hider:samplestride", + "label": "Sample Stride", + "type": "int", + "default": 1, + "help": "This facilitates rendering several images in parallel (with the same samplestride but different sampleoffset) and then combine them incrementally during interactive rendering. Default value is 1.

With non-adaptive sampling:
Let's say you render four images with 256 samples each, with samplestride 4 and sampleoffsets 0, 1, 2, and 3. If you combine those four images, you'll get exactly the same image as if you had rendered a single image with 1024 samples, and during incremental/interactive rendering you'll get the benefit of best use of the stratified progressive sample sequences. If you can 'assemble' images with samples 0-3, 4-7, 8-12, etc during interactive rendering, you've got the benefits of faster rendering without jumping to high samples early on.

With adaptive sampling:
Let's say you again render four images, each with 'maxsamples' 256, with samplestride 4 and sampleoffsets 0, 1, 2, and 3. This time with adaptive sampling (PixelVariance > 0). Since each of the four images is more noisy than the combined image, the adaptive sampling will use more samples before deciding that the image error is acceptable than if it had been rendered as a single image. So the PixelVariance setting may need to be adjusted." + }, { "panel": "RENDER_PT_renderman_sampling", "name": "hider_incremental", @@ -556,6 +586,16 @@ "widget": "checkbox", "help": "Incremental mode means the renderer visits every pixel in the image, computing a single sample for each, then does the same for a second sample, etc. In non-incremental mode, the renderer visits each pixel once and computes all the samples in one go." }, + { + "panel": "RENDER_PT_renderman_sampling", + "name": "hider_jitter", + "riopt": "hider:jitter", + "label": "Jitter", + "type": "int", + "default": 1, + "widget": "checkbox", + "help": "This option toggles the jitter (variation) in camera samples. By default, this hider option is enabled (set to 1), meaning that camera samples will be placed randomly in a pixel location. When it is turned off, camera rays are placed in the center of the pixel. When turned off, surface edges will appear aliased and jagged." + }, { "panel": "RENDER_PT_renderman_sampling", "page": "Global Trace Settings", @@ -563,8 +603,8 @@ "riattr": "trace:maxspeculardepth", "label": "Max Specular Depth", "type": "int", - "editable": true, - "update_function_name": "update_root_node_func", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_root_node_func(s, 'ri_maxSpecularDepth', c)", "default": 4, "min": 0, "slidermax": 15 @@ -576,8 +616,8 @@ "riattr": "trace:maxdiffusedepth", "label": "Max Diffuse Depth", "type": "int", - "editable": true, - "update_function_name": "update_root_node_func", + "ipr_editable": true, + "update_function_name": "lambda s, c: update_root_node_func(s, 'ri_maxDiffuseDepth', c)", "default": 1, "min": 0, "slidermax": 15 @@ -796,6 +836,17 @@ "widget": "checkbox", "help": "Experimental scheme to use earlier lighting results to drive selection of lights in later iterations." }, + { + "panel": "RENDER_PT_renderman_advanced_settings", + "name": "hider_bluenoise", + "riopt": "hider:bluenoise", + "label": "Blue Noise Samples", + "type": "int", + "default": 1, + "widget": "checkbox", + "connectable": false, + "help": "Setting to control whether the samples should have a visually pleasing blue noise distribution or not. Default is 1 (on). Set to 0 (off) if the samples in adjacent pixels need to be completely decorrelated." + }, { "panel": "RENDER_PT_renderman_advanced_settings", "name": "shade_roughnessmollification", @@ -816,6 +867,7 @@ "type": "string", "default": "gaussian", "widget": "popup", + "help": "The filter for rendered pixels. The box, triangle, disk, gaussian, and blackman-harris are softening filters while catmull-rom, sinc, mitchell, separable-catmull-rom, lanczos and bessel are sharpening filters. Separable-catmull-rom is a good compromise.", "options": "box|triangle|catmull-rom|sinc|gaussian|mitchell|separable-catmull-rom|blackman-harris|lanczos|bessel|disk" }, { @@ -829,6 +881,7 @@ 2, 2 ], + "help": "The width of the pixel filter. Typically from 1 to 6.", "connectable": false }, { @@ -897,6 +950,7 @@ 16 ], "widget": "mapper", + "help": "Specify the size of the buckets. Larger buckets will require more memory to render. Conversely, smaller buckets are less efficient for shading, but will use less memory during rendering. If your scene is using a lot of memory, you may want to try setting this field down to 8 by 8 or even 4 by 4 buckets, but most likely you will not change this.", "options": "4x4:[4,4]|8x8:[8,8]|16x16:[16,16]|32x32:[32,32]|64x64:[64,64]", "connectable": false }, @@ -908,6 +962,7 @@ "type": "string", "default": "circle", "widget": "popup", + "help": "RenderMan subdivides the output image into small rectangular regions called buckets, and renders a single bucket at a time. This allows a small portion of the image to be in memory at any one time. Note that only some bucket orders are supported for checkpointing and recovery.", "options": "horizontal|vertical|zigzag-x|zigzag-y|spiral|spacefill|circle" }, { @@ -940,6 +995,17 @@ "conditionalVisValue": "spiral" } }, + { + "panel": "RENDER_PT_renderman_advanced_settings", + "name": "volume_aggregatespace", + "label": "Volume Aggregate Space", + "riopt": "volume:aggregatespace", + "type": "string", + "default": "world", + "widget": "popup", + "help": "Space in which to compute aggregate volume metadata. Valid spaces are 'world' and 'camera'. Generally the space should be chosen to minimize the range of motion of the volumes.", + "options": "world|camera" + }, { "panel": "RENDER_PT_renderman_advanced_settings", "page": "Open Shading Language", @@ -1369,7 +1435,58 @@ "widget": "mapper", "options": "LocalQueue:lq|Tractor:tractor|None:none", "help": "System to spool to. None will generate RIB files, but not spool a render." - }, + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "", + "name": "bl_batch_frame_chunk", + "label": "Frame Chunking", + "type": "int", + "default": 1, + "min": 1, + "help": "Frame chunking. Determines how many frames each Blender batch task will render.", + "conditionalVisOps": { + "conditionalVisOp": "equalTo", + "conditionalVisPath": "spool_style", + "conditionalVisValue": "batch" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "", + "name": "use_bl_compositor", + "label": "Use Blender Compositor", + "type": "int", + "default": 1, + "widget": "checkbox", + "help": "Only applies to Blender Batch. When enabled, all of the RenderMan Display settings are ignored, and one final image from the Blender compositor is written out. When using RIB, the Blender compositor cannot be used.", + "conditionalVisOps": { + "conditionalVisOp": "equalTo", + "conditionalVisPath": "spool_style", + "conditionalVisValue": "batch" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "", + "name": "use_bl_compositor_write_aovs", + "label": "Blender Batch AOVs", + "type": "int", + "default": 0, + "widget": "checkbox", + "help": "Only applies to Blender Batch. If on, and Use Blender Compositor is also turned on, all AOVs will be written out as OpenEXR files.", + "conditionalVisOps": { + "conditionalVis1Path": "spool_style", + "conditionalVis2Op": "equalTo", + "conditionalVis1Value": "batch", + "conditionalVisOp": "and", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "use_bl_compositor", + "conditionalVis2Value": "1" + } + }, { "panel": "RENDER_PT_renderman_spooling_export_options", "page": "", @@ -1378,7 +1495,12 @@ "type": "int", "default": 0, "widget": "checkbox", - "help": "Attempt to resume render from a previous checkpoint (if possible)." + "help": "Attempt to resume render from a previous checkpoint (if possible). This is only avaliable for RIB renders.", + "conditionalVisOps": { + "conditionalVisOp": "equalTo", + "conditionalVisPath": "spool_style", + "conditionalVisValue": "rib" + } }, { "panel": "RENDER_PT_renderman_spooling_export_options", @@ -1411,29 +1533,127 @@ "default": "", "help": "Inserts a string of custom command arguments into the render process" }, + { + "panel": "", + "page": "Denoising", + "name": "use_legacy_denoiser", + "label": "Use Legacy Denoiser", + "type": "int", + "default": 0, + "widget": "checkbox", + "help": "Use the previous denoiser to denoise passes." + }, { "panel": "RENDER_PT_renderman_spooling_export_options", - "page": "", + "page": "Denoising", + "name": "ai_denoiser_asymmetry", + "label": "Asymmetry", + "type": "float", + "default": 0.0, + "min": 0.0, + "max": 2.0, + "help": "Controls the asymmetry value, 0 is best quality, higher values encourage the denoiser to avoid overblurring, leading to weaker denoising.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "use_legacy_denoiser", + "conditionalVisValue": "1" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "Denoising", + "name": "ai_denoiser_output_dir", + "label": "Output Directory", + "type": "string", + "widget": "dirinput", + "default": "", + "help": "The output directory for the denoised images. If not set, we put the images into a subfolder 'denoised' relative to the input images.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "use_legacy_denoiser", + "conditionalVisValue": "1" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "Denoising", + "name": "ai_denoiser_mode", + "label": "Mode", + "type": "string", + "default": "singleframe", + "widget": "mapper", + "options": "Single Frame:singleframe|Cross Frame:crossframe", + "help": "Use single frame or cross frame. Cross frame requires at least 7 frames.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "use_legacy_denoiser", + "conditionalVisValue": "1" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "Denoising", + "name": "ai_denoiser_flow", + "label": "Flow", + "type": "int", + "default": 1, + "widget": "checkbox", + "help": "Whether to compute optical flow.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "use_legacy_denoiser", + "conditionalVisValue": "1" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "Denoising", + "name": "ai_denoiser_verbose", + "label": "Verbose", + "type": "int", + "default": 0, + "widget": "checkbox", + "help": "Enable versbose output.", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "use_legacy_denoiser", + "conditionalVisValue": "1" + } + }, + { + "panel": "RENDER_PT_renderman_spooling_export_options", + "page": "Denoising", "name": "denoise_cmd", "label": "Custom Denoise Commands", "type": "string", "default": "", - "help": "Inserts a string of custom commands arguments into the denoising process, if selected" + "help": "Inserts a string of custom commands arguments into the denoising process (hyperion), if selected", + "conditionalVisOps": { + "conditionalVisOp": "notEqualTo", + "conditionalVisPath": "use_legacy_denoiser", + "conditionalVisValue": "0" + } }, { "panel": "RENDER_PT_renderman_spooling_export_options", - "page": "", + "page": "Denoising", "name": "denoise_gpu", "label": "Use GPU for denoising", "type": "int", "default": 0, "widget": "checkbox", - "help": "The denoiser will attempt to use the GPU (if available)", + "help": "The denoiser (hyperion) will attempt to use the GPU (if available)", "conditionalVisOps": { - "conditionalVisOp": "notEqualTo", - "conditionalVisPath": "current_platform", - "conditionalVisValue": "macOS" - } + "conditionalVis1Path": "use_legacy_denoiser", + "conditionalVis2Op": "notEqualTo", + "conditionalVis1Value": "1", + "conditionalVisOp": "and", + "conditionalVis1Op": "equalTo", + "conditionalVisLeft": "conditionalVis1", + "conditionalVisRight": "conditionalVis2", + "conditionalVis2Path": "current_platform", + "conditionalVis2Value": "macOS" + } } ] } \ No newline at end of file diff --git a/rman_config/config/rman_properties_volume.json b/rman_config/config/rman_properties_volume.json index b0148785..560011ad 100644 --- a/rman_config/config/rman_properties_volume.json +++ b/rman_config/config/rman_properties_volume.json @@ -6,7 +6,7 @@ "name": "openvdb_filterwidth", "label": "Filter Width", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "", "help": "If set to 0, disables mipmapping. If greater than 0, values less than 1 bias towards finer levels of the mipmap and values larger than one bias towards coarser levels." @@ -16,7 +16,7 @@ "name": "openvdb_densitymult", "label": "Density Multiplier", "type": "float", - "editable": true, + "ipr_editable": true, "default": 1.0, "page": "", "help": "Scales the values in the density grid of the volume. This should be more efficient than scaling the density in the shader." @@ -26,7 +26,7 @@ "name": "openvdb_densityrolloff", "label": "Density Rolloff", "type": "float", - "editable": true, + "ipr_editable": true, "default": 0.0, "page": "", "help": "Values greater than 0 produce a logarithmic falloff in density values. Smaller rolloff values produce greater falloff." @@ -36,12 +36,26 @@ "name": "volume_dsominmax", "label": "DSO Min/Max", "type": "int", - "editable": true, + "ipr_editable": true, "default": 1, "widget": "checkbox", - "riattr": "volume:dsominmax", + "primvar": "volume:dsominmax", "page": "", - "help": "Currently only used for aggregate volumes, and only for volumes that use an ImplicitField DSO. If set to 1, the DSO may be able to use stored information from the file directly to compute the minimum and maximum values within the volume. This may allow the renderer to avoid shading the volume up front, greatly decreasing time to first pixel. This can only be enabled if the density signal from the volume is used directly, or if the density signal is modulated only by the DSO itself. Any shading modifications of the density signal requires setting this parameter to off." + "help": "Currently only used for aggregate volumes, and only for volumes that use an ImplicitField DSO. If set to 1, the DSO may be able to use stored information from the file directly to compute the minimum and maximum values within the volume. This may allow the renderer to avoid shading the volume up front, greatly decreasing time to first pixel. This can only be enabled if the density signal from the volume is used directly, or if the density signal is modulated only by the DSO itself. Any shading modifications of the density signal requires setting this parameter to off.", + "update_function_name": "lambda s, c: update_primvar_func(s, 'volume_dsominmax', c)" + }, + { + "panel": "VOLUME_PT_renderman_openvdb_attributes", + "name": "volume_dsovelocity", + "label": "DSO Velocity", + "type": "int", + "ipr_editable": true, + "default": 0, + "widget": "checkbox", + "primvar": "volume:dsovelocity", + "page": "", + "help": "Used only for aggregate volumes that use an ImplicitField DSO. If set to 1, the DSO can provide velocity bounds to the renderer directly via the BoxMotion call. This can allow the renderer to avoid upfront shading of the volume's velocity data when using Eulerian velocity, improving render time and time to first pixel greatly. This can only be enabled if the velocity signal from the DSO is used directly.", + "update_function_name": "lambda s, c: update_primvar_func(s, 'volume_dsovelocity', c)" } ] } \ No newline at end of file diff --git a/rman_config/config/schemas/rfbSchema.json b/rman_config/config/schemas/rfbSchema.json index cbe4c14b..aada99b5 100644 --- a/rman_config/config/schemas/rfbSchema.json +++ b/rman_config/config/schemas/rfbSchema.json @@ -35,11 +35,46 @@ }, "user": { "type": "string" + }, + "priority": { + "type": "number" + }, + "service": { + "type": "string" + }, + "envkeys": { + "type": "string" + }, + "after": { + "type": "string" + }, + "crews": { + "type": "string" + }, + "tier": { + "type": "string" + }, + "projects": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "metdata": { + "type": "string" + }, + "whendone": { + "type": "string" + }, + "whenerror": { + "type": "string" + }, + "whenalways": { + "type": "string" } + }, "required": [ - "engine", - "port" ] }, "bxdf_viewport_color_map": { diff --git a/rman_constants.py b/rman_constants.py index 664456d6..4c854516 100644 --- a/rman_constants.py +++ b/rman_constants.py @@ -1,8 +1,9 @@ import os import bpy +import sys -RFB_ADDON_VERSION_MAJOR = 24 -RFB_ADDON_VERSION_MINOR = 4 +RFB_ADDON_VERSION_MAJOR = 25 +RFB_ADDON_VERSION_MINOR = 0 RFB_ADDON_VERSION_PATCH = 0 RFB_ADDON_VERSION = (RFB_ADDON_VERSION_MAJOR, RFB_ADDON_VERSION_MINOR, RFB_ADDON_VERSION_PATCH) RFB_ADDON_VERSION_STRING = '%d.%d.%d' % (RFB_ADDON_VERSION_MAJOR, RFB_ADDON_VERSION_MINOR, RFB_ADDON_VERSION_PATCH) @@ -19,18 +20,21 @@ BLENDER_SUPPORTED_VERSION_PATCH = 0 BLENDER_SUPPORTED_VERSION = (BLENDER_SUPPORTED_VERSION_MAJOR, BLENDER_SUPPORTED_VERSION_MINOR, BLENDER_SUPPORTED_VERSION_PATCH) -BLENDER_PYTHON_VERSION = "3.7" -if BLENDER_VERSION_MAJOR > 2: - BLENDER_PYTHON_VERSION = "3.9" -elif BLENDER_VERSION_MAJOR == 2 and BLENDER_VERSION_MINOR >= 93: - BLENDER_PYTHON_VERSION = "3.9" +pyver = sys.version_info +BLENDER_PYTHON_VERSION_MAJOR = pyver.major +BLENDER_PYTHON_VERSION_MINOR = pyver.minor +BLENDER_PYTHON_VERSION = '%s.%s' % (pyver.major, pyver.minor) -RMAN_SUPPORTED_VERSION_MAJOR = 24 -RMAN_SUPPORTED_VERSION_MINOR = 4 +RMAN_SUPPORTED_VERSION_MAJOR = 25 +RMAN_SUPPORTED_VERSION_MINOR = 0 RMAN_SUPPORTED_VERSION_BETA = '' RMAN_SUPPORTED_VERSION = (RMAN_SUPPORTED_VERSION_MAJOR, RMAN_SUPPORTED_VERSION_MINOR, RMAN_SUPPORTED_VERSION_BETA) RMAN_SUPPORTED_VERSION_STRING = '%d.%d%s' % (RMAN_SUPPORTED_VERSION_MAJOR, RMAN_SUPPORTED_VERSION_MINOR, RMAN_SUPPORTED_VERSION_BETA) +RFB_SCENE_VERSION_MAJOR = RMAN_SUPPORTED_VERSION_MAJOR +RFB_SCENE_VERSION_MINOR = RMAN_SUPPORTED_VERSION_MINOR +RFB_SCENE_VERSION_PATCH = 1 +RFB_SCENE_VERSION_STRING = '%d.%d.%d' % (RFB_SCENE_VERSION_MAJOR, RFB_SCENE_VERSION_MINOR, RFB_SCENE_VERSION_PATCH) RFB_ADDON_DESCRIPTION = 'RenderMan %d.%d integration' % (RMAN_SUPPORTED_VERSION_MAJOR, RMAN_SUPPORTED_VERSION_MINOR) @@ -40,6 +44,10 @@ RFB_VIEWPORT_MAX_BUCKETS = 10 RFB_PREFS_NAME = __name__.split('.')[0] RMAN_RENDERMAN_BLUE = (0.0, 0.498, 1.0, 1.0) +RMAN_FAKE_NODEGROUP = '.__RMAN_FAKE_NODEGROUP__' +RMAN_GLOBAL_VOL_AGGREGATE = 'globalVolumeAggregate' + +RFB_HELP_URL = "https://rmanwiki.pixar.com/display/RFB%s" % RMAN_SUPPORTED_VERSION_MAJOR RFB_FLOAT3 = ['color', 'point', 'vector', 'normal'] RFB_FLOATX = ['color', 'point', 'vector', 'normal', 'matrix'] diff --git a/rman_cycles_convert/__init__.py b/rman_cycles_convert/__init__.py index 4ee0817a..7b14382b 100644 --- a/rman_cycles_convert/__init__.py +++ b/rman_cycles_convert/__init__.py @@ -37,30 +37,31 @@ def convert_cycles_bsdf(nt, rman_parent, node, input_index): elif not node1: rman_node2 = convert_cycles_bsdf(nt, rman_parent, node2, input_index) if rman_parent.bl_label == 'LamaSurface': - nt.links.new(rman_node2.outputs["Bxdf"], + nt.links.new(rman_node2.outputs["bxdf_out"], rman_parent.inputs['materialFront']) else: - nt.links.new(rman_node2.outputs["Bxdf"], + nt.links.new(rman_node2.outputs["bxdf_out"], rman_parent.inputs[input_index]) elif not node2: rman_node1 = convert_cycles_bsdf(nt, rman_parent, node1, input_index) if rman_parent.bl_label == 'LamaSurface': - nt.links.new(rman_node1.outputs["Bxdf"], + nt.links.new(rman_node1.outputs["bxdf_out"], rman_parent.inputs['materialFront']) else: - nt.links.new(rman_node1.outputs["Bxdf"], + nt.links.new(rman_node1.outputs["bxdf_out"], rman_parent.inputs[input_index]) elif node.bl_idname == 'ShaderNodeAddShader': - + node1.hide = True + node2.hide = True node_name = __BL_NODES_MAP__.get('LamaAdd') add = nt.nodes.new(node_name) if rman_parent.bl_label == 'LamaSurface': - nt.links.new(add.outputs["Bxdf"], + nt.links.new(add.outputs["bxdf_out"], rman_parent.inputs['materialFront']) else: - nt.links.new(add.outputs["Bxdf"], + nt.links.new(add.outputs["bxdf_out"], rman_parent.inputs[input_index]) offset_node_location(rman_parent, add, node) @@ -68,9 +69,9 @@ def convert_cycles_bsdf(nt, rman_parent, node, input_index): rman_node1 = convert_cycles_bsdf(nt, add, node1, 0) rman_node2 = convert_cycles_bsdf(nt, add, node2, 1) - nt.links.new(rman_node1.outputs["Bxdf"], + nt.links.new(rman_node1.outputs["bxdf_out"], add.inputs['material1']) - nt.links.new(rman_node2.outputs["Bxdf"], + nt.links.new(rman_node2.outputs["bxdf_out"], add.inputs['material2']) setattr(add, "weight1", 0.5) @@ -79,14 +80,16 @@ def convert_cycles_bsdf(nt, rman_parent, node, input_index): return add elif node.bl_idname == 'ShaderNodeMixShader': + node1.hide = True + node2.hide = True node_name = __BL_NODES_MAP__.get('LamaMix') mixer = nt.nodes.new(node_name) if rman_parent.bl_label == 'LamaSurface': - nt.links.new(mixer.outputs["Bxdf"], + nt.links.new(mixer.outputs["bxdf_out"], rman_parent.inputs['materialFront']) else: - nt.links.new(mixer.outputs["Bxdf"], + nt.links.new(mixer.outputs["bxdf_out"], rman_parent.inputs[input_index]) offset_node_location(rman_parent, mixer, node) @@ -97,9 +100,9 @@ def convert_cycles_bsdf(nt, rman_parent, node, input_index): rman_node1 = convert_cycles_bsdf(nt, mixer, node1, 0) rman_node2 = convert_cycles_bsdf(nt, mixer, node2, 1) - nt.links.new(rman_node1.outputs["Bxdf"], + nt.links.new(rman_node1.outputs["bxdf_out"], mixer.inputs['material1']) - nt.links.new(rman_node2.outputs["Bxdf"], + nt.links.new(rman_node2.outputs["bxdf_out"], mixer.inputs['material2']) return mixer @@ -110,7 +113,7 @@ def convert_cycles_bsdf(nt, rman_parent, node, input_index): convert_func(nt, node, rman_node) if rman_parent.bl_label == 'LamaSurface': - nt.links.new(rman_node.outputs["Bxdf"], + nt.links.new(rman_node.outputs["bxdf_out"], rman_parent.inputs['materialFront']) if node.bl_idname == 'ShaderNodeBsdfTransparent': setattr(rman_parent, 'presence', 0.0) @@ -121,7 +124,7 @@ def convert_cycles_bsdf(nt, rman_parent, node, input_index): node_name = __BL_NODES_MAP__.get('LamaDiffuse') rman_node = nt.nodes.new(node_name) if rman_parent.bl_label == 'LamaSurface': - nt.links.new(rman_node.outputs["Bxdf"], + nt.links.new(rman_node.outputs["bxdf_out"], rman_parent.inputs['materialFront']) return rman_node @@ -135,7 +138,7 @@ def convert_cycles_displacement(nt, displace_socket, output_node): node_name = __BL_NODES_MAP__.get('PxrDisplace') displace = nt.nodes.new(node_name) - nt.links.new(displace.outputs[0], output_node.inputs['Displacement']) + nt.links.new(displace.outputs[0], output_node.inputs['displace_in']) displace.location = output_node.location displace.location[0] -= 200 displace.location[1] -= 100 @@ -218,7 +221,7 @@ def convert_cycles_nodetree(id, output_node): if begin_cycles_node.bl_idname == "ShaderNodeEmission": node_name = __BL_NODES_MAP__.get('PxrMeshLight') meshlight = nt.nodes.new(node_name) - nt.links.new(meshlight.outputs[0], output_node.inputs["Light"]) + nt.links.new(meshlight.outputs[0], output_node.inputs["light_in"]) offset_node_location(output_node, meshlight, begin_cycles_node) convert_cycles_input(nt, begin_cycles_node.inputs[ 'Strength'], meshlight, "intensity") @@ -229,7 +232,7 @@ def convert_cycles_nodetree(id, output_node): setattr(meshlight, 'lightColor', begin_cycles_node.inputs[ 'Color'].default_value[:3]) bxdf = nt.nodes.new('PxrBlackBxdfNode') - nt.links.new(bxdf.outputs[0], output_node.inputs["Bxdf"]) + nt.links.new(bxdf.outputs[0], output_node.inputs["bxdf_in"]) else: if begin_cycles_node.bl_idname == "ShaderNodeBsdfPrincipled": # use PxrDisneyBsdf @@ -251,8 +254,7 @@ def convert_cycles_nodetree(id, output_node): rman_node.location = output_node.location convert_func(nt, begin_cycles_node, rman_node) - nt.links.new(rman_node.outputs['Bxdf'], base_surface.inputs["materialFront"]) - #offset_node_location(output_node, base_surface, begin_cycles_node) + nt.links.new(rman_node.outputs['bxdf_out'], base_surface.inputs["materialFront"]) base_surface.location = output_node.location base_surface.location[0] -= 160.0 rman_node.location[0] -= 320.0 @@ -274,6 +276,8 @@ def convert_cycles_nodetree(id, output_node): base_surface.update_mat(id) has_bxdf = True elif cycles_output_node.inputs['Volume'].is_linked: + begin_cycles_node.hide = True + cycles_output_node.hide = True begin_cycles_node = cycles_output_node.inputs['Volume'].links[0].from_node node_type = begin_cycles_node.bl_idname bxdf_name, convert_func = _BSDF_MAP_.get(node_type, (None, None)) @@ -281,12 +285,12 @@ def convert_cycles_nodetree(id, output_node): node_name = __BL_NODES_MAP__.get(bxdf_name) rman_node = nt.nodes.new(node_name) convert_func(nt, begin_cycles_node, rman_node) - nt.links.new(rman_node.outputs[0], output_node.inputs["Bxdf"]) + nt.links.new(rman_node.outputs[0], output_node.inputs["bxdf_in"]) has_volume = True else: rfb_log().warning("Could not convert cycles volume node: %s" % begin_cycles_node.name) - elif cycles_output_node.inputs['Displacement'].is_linked: + if cycles_output_node.inputs['Displacement'].is_linked: convert_cycles_displacement(nt, cycles_output_node.inputs['Displacement'], output_node) has_displacement = True diff --git a/rman_cycles_convert/cycles_convert.py b/rman_cycles_convert/cycles_convert.py index d0883fa2..7054be56 100644 --- a/rman_cycles_convert/cycles_convert.py +++ b/rman_cycles_convert/cycles_convert.py @@ -33,6 +33,7 @@ def convert_cycles_node(nt, node, location=None): node_type = node.bl_idname + node.hide = True if node.name in converted_nodes: return nt.nodes[converted_nodes[node.name]] @@ -73,6 +74,8 @@ def convert_cycles_node(nt, node, location=None): node2 = node.inputs[ 1 + i].links[0].from_node if node.inputs[1 + i].is_linked else None + node1.hide = True + node2.hide = True if node.bl_idname == 'ShaderNodeAddShader': node_name = __BL_NODES_MAP__.get('LamaAdd') add = nt.nodes.new(node_name) @@ -83,9 +86,9 @@ def convert_cycles_node(nt, node, location=None): rman_node1 = convert_cycles_bsdf(nt, add, node1, 0) rman_node2 = convert_cycles_bsdf(nt, add, node2, 1) - nt.links.new(rman_node1.outputs["Bxdf"], + nt.links.new(rman_node1.outputs["bxdf_out"], add.inputs['material1']) - nt.links.new(rman_node2.outputs["Bxdf"], + nt.links.new(rman_node2.outputs["bxdf_out"], add.inputs['material2']) setattr(add, "weight1", 0.5) @@ -106,9 +109,9 @@ def convert_cycles_node(nt, node, location=None): rman_node1 = convert_cycles_bsdf(nt, mixer, node1, 0) rman_node2 = convert_cycles_bsdf(nt, mixer, node2, 1) - nt.links.new(rman_node1.outputs["Bxdf"], + nt.links.new(rman_node1.outputs["bxdf_out"], mixer.inputs['material1']) - nt.links.new(rman_node2.outputs["Bxdf"], + nt.links.new(rman_node2.outputs["bxdf_out"], mixer.inputs['material2']) return mixer @@ -151,11 +154,95 @@ def set_color_space(nt, socket, rman_node, node, param_name, in_socket): if node.bl_label in ['PxrTexture'] and shadergraph_utils.is_socket_float_type(in_socket): setattr(node, 'filename_colorspace', 'data') +def convert_new_geometry_node(nt, socket, cycles_node, rman_node, param_name): + socket_nm = socket.links[0].from_socket.name + in_socket = rman_node.inputs[param_name] + if socket_nm == 'Backfacing': + node_name = __BL_NODES_MAP__.get('PxrShadedSide', None) + convert_node = nt.nodes.new(node_name) + convert_node.invert = 1 + nt.links.new(convert_node.outputs['resultF'], in_socket) + elif socket_nm == 'Incoming': + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'Vn' + convert_node.type = 'vector' + nt.links.new(convert_node.outputs['resultP'], in_socket) + elif socket_nm == 'Normal': + # The Blender docs says this also includes bump mapping + # Have to think about how to wire the result of any PxrBumps in the network + # to here + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'Nn' + convert_node.type = 'normal' + nt.links.new(convert_node.outputs['resultP'], in_socket) + elif socket_nm == 'Parametric': + # From the Blender docs: + # + # "Parametric coordinates of the shading point on the surface. + # To area lights it outputs its UV coordinates in planar mapping and + # in spherical coordinates to point lights." + # + # + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'uvw' + convert_node.type = 'vector' + nt.links.new(convert_node.outputs['resultP'], in_socket) + elif socket_nm == 'Pointiness': + # From the Blender docs: + # + # "An approximation of the curvature of the mesh per vertex. Lighter + # values indicate convex angles, darker values indicate concave angles. + # It allows you to do effects like dirt maps and wear-off effects." + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'curvature' + convert_node.type = 'float' + nt.links.new(convert_node.outputs['resultF'], in_socket) + elif socket_nm == 'Position': + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'P' + convert_node.type = 'point' + nt.links.new(convert_node.outputs['resultP'], in_socket) + elif socket_nm == 'Random Per Island': + # From the Blender docs: + # + # "A random value for each connected component (island) of the mesh. + # It is useful to add variations to meshes composed of separated units like + # tree leaves, wood planks, or curves of multiple splines." + # + # Not exactly sure how to convert this. For now, we'll just use PxrVary. + # PxrVary doesn't have a float output, so we'll just use resultR + node_name = __BL_NODES_MAP__.get('PxrVary', None) + convert_node = nt.nodes.new(node_name) + nt.links.new(convert_node.outputs['resultR'], in_socket) + elif socket_nm == 'Tangent': + # Tangent at the surface. + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'Tn' + convert_node.type = 'vector' + nt.links.new(convert_node.outputs['resultP'], in_socket) + elif socket_nm == 'True Normal': + # Geometry or flat normal of the surface. + node_name = __BL_NODES_MAP__.get('PxrPrimvar', None) + convert_node = nt.nodes.new(node_name) + convert_node.variable = 'Ngn' + convert_node.type = 'normal' + nt.links.new(convert_node.outputs['resultP'], in_socket) def convert_linked_node(nt, socket, rman_node, param_name): location = rman_node.location - \ (socket.node.location - socket.links[0].from_node.location) - node = convert_cycles_node(nt, socket.links[0].from_node, location) + from_node = socket.links[0].from_node + if from_node.bl_idname == 'ShaderNodeNewGeometry': + # this node needs special handling + return convert_new_geometry_node(nt, socket, from_node, rman_node, param_name) + + node = convert_cycles_node(nt, from_node, location) if node: out_socket = None @@ -367,6 +454,21 @@ def convert_math_node(nt, cycles_node, rman_node): return +def convert_wireframe_node(nt, cycles_node, rman_node): + + tmp = [rman_node.wireColor[0], rman_node.wireColor[1], rman_node.wireColor[2]] + rman_node.wireColor = [rman_node.backColor[0], rman_node.backColor[1], rman_node.backColor[2]] + rman_node.backColor = [tmp[0], tmp[1], tmp[2]] + + input = cycles_node.inputs['Size'] + if input.is_linked: + convert_linked_node(nt, input, rman_node, input.name) + else: + val = input.default_value + rman_node.wireWidth = val * 100.0 + + return + # this needs a special case to init the stuff @@ -455,9 +557,9 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): diffuse_node.location[0] -= 1280.0 nodes_list.append(diffuse_node) - nt.links.new(rman_node.outputs["out_baseColor"], diffuse_node.inputs["color"]) + nt.links.new(rman_node.outputs["out_baseColor"], diffuse_node.inputs["diffuseColor"]) nt.links.new(rman_node.outputs["out_roughness"], diffuse_node.inputs["roughness"]) - nt.links.new(rman_node.outputs["out_normal"], diffuse_node.inputs["normal"]) + nt.links.new(rman_node.outputs["out_normal"], diffuse_node.inputs["diffuseNormal"]) # subsurface node_name = __BL_NODES_MAP__.get('LamaSSS', None) @@ -467,8 +569,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): sss_node.location[1] -= 240.0 nodes_list.append(sss_node) - nt.links.new(rman_node.outputs["out_sssColor"], sss_node.inputs["color"]) - nt.links.new(rman_node.outputs["out_normal"], sss_node.inputs["normal"]) + nt.links.new(rman_node.outputs["out_sssColor"], sss_node.inputs["sssColor"]) + nt.links.new(rman_node.outputs["out_normal"], sss_node.inputs["sssNormal"]) nt.links.new(rman_node.outputs["out_sssRadius"], sss_node.inputs["sssRadius"]) # diff or sss mix @@ -479,8 +581,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): diff_sss_mix_node.location[0] -= 1120.0 nodes_list.append(diff_sss_mix_node) - nt.links.new(diffuse_node.outputs["Bxdf"], diff_sss_mix_node.inputs["material1"]) - nt.links.new(sss_node.outputs["Bxdf"], diff_sss_mix_node.inputs["material2"]) + nt.links.new(diffuse_node.outputs["bxdf_out"], diff_sss_mix_node.inputs["material1"]) + nt.links.new(sss_node.outputs["bxdf_out"], diff_sss_mix_node.inputs["material2"]) nt.links.new(rman_node.outputs["out_sssMix"], diff_sss_mix_node.inputs["mix"]) # sheen @@ -491,8 +593,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): sheen_node.location[1] -= 240.0 nodes_list.append(sheen_node) - nt.links.new(rman_node.outputs["out_sheenColor"], sheen_node.inputs["color"]) - nt.links.new(rman_node.outputs["out_normal"], sheen_node.inputs["normal"]) + nt.links.new(rman_node.outputs["out_sheenColor"], sheen_node.inputs["sheenColor"]) + nt.links.new(rman_node.outputs["out_normal"], sheen_node.inputs["sheenNormal"]) # diff sheen add node_name = __BL_NODES_MAP__.get('LamaAdd', None) @@ -502,8 +604,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): diff_sheen_add_node.location[0] -= 960.0 nodes_list.append(diff_sheen_add_node) - nt.links.new(diff_sss_mix_node.outputs["Bxdf"], diff_sheen_add_node.inputs["material1"]) - nt.links.new(sheen_node.outputs["Bxdf"], diff_sheen_add_node.inputs["material2"]) + nt.links.new(diff_sss_mix_node.outputs["bxdf_out"], diff_sheen_add_node.inputs["material1"]) + nt.links.new(sheen_node.outputs["bxdf_out"], diff_sheen_add_node.inputs["material2"]) nt.links.new(rman_node.outputs["out_sheenWeight"], diff_sheen_add_node.inputs["weight2"]) # specular @@ -517,7 +619,7 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): nt.links.new(rman_node.outputs["out_specF0"], specular_node.inputs["reflectivity"]) nt.links.new(rman_node.outputs["out_roughness"], specular_node.inputs["roughness"]) - nt.links.new(rman_node.outputs["out_normal"], specular_node.inputs["normal"]) + nt.links.new(rman_node.outputs["out_normal"], specular_node.inputs["conductorNormal"]) nt.links.new(rman_node.outputs["out_anisotropic"], specular_node.inputs["anisotropy"]) nt.links.new(rman_node.outputs["out_anisotropicRotation"], specular_node.inputs["anisotropyRotation"]) @@ -529,8 +631,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): sheen_spec_add_node.location[0] -= 800.0 nodes_list.append(sheen_spec_add_node) - nt.links.new(diff_sheen_add_node.outputs["Bxdf"], sheen_spec_add_node.inputs["material1"]) - nt.links.new(specular_node.outputs["Bxdf"], sheen_spec_add_node.inputs["material2"]) + nt.links.new(diff_sheen_add_node.outputs["bxdf_out"], sheen_spec_add_node.inputs["material1"]) + nt.links.new(specular_node.outputs["bxdf_out"], sheen_spec_add_node.inputs["material2"]) nt.links.new(rman_node.outputs["out_diffuseWeight"], sheen_spec_add_node.inputs["weight1"]) nt.links.new(rman_node.outputs["out_specularWeight"], sheen_spec_add_node.inputs["weight2"]) @@ -546,7 +648,7 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): nt.links.new(rman_node.outputs["out_baseColor"], transmission_node.inputs["transmissionTint"]) nt.links.new(rman_node.outputs["out_roughness"], transmission_node.inputs["roughness"]) - nt.links.new(rman_node.outputs["out_normal"], transmission_node.inputs["normal"]) + nt.links.new(rman_node.outputs["out_normal"], transmission_node.inputs["dielectricNormal"]) # spec transmission add node_name = __BL_NODES_MAP__.get('LamaAdd', None) @@ -556,8 +658,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): spec_transmission_add_node.location[0] -= 640.0 nodes_list.append(spec_transmission_add_node) - nt.links.new(sheen_spec_add_node.outputs["Bxdf"], spec_transmission_add_node.inputs["material1"]) - nt.links.new(transmission_node.outputs["Bxdf"], spec_transmission_add_node.inputs["material2"]) + nt.links.new(sheen_spec_add_node.outputs["bxdf_out"], spec_transmission_add_node.inputs["material1"]) + nt.links.new(transmission_node.outputs["bxdf_out"], spec_transmission_add_node.inputs["material2"]) nt.links.new(rman_node.outputs["out_finalTransmission"], spec_transmission_add_node.inputs["weight2"]) # coat @@ -570,7 +672,7 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): nodes_list.append(coat_node) nt.links.new(rman_node.outputs["out_clearcoatRoughness"], coat_node.inputs["roughness"]) - nt.links.new(rman_node.outputs["out_clearcoatNormal"], coat_node.inputs["normal"]) + nt.links.new(rman_node.outputs["out_clearcoatNormal"], coat_node.inputs["dielectricNormal"]) # transmission coat add node_name = __BL_NODES_MAP__.get('LamaAdd', None) @@ -580,8 +682,8 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): transmission_coat_add_node.location[0] -= 480.0 nodes_list.append(transmission_coat_add_node) - nt.links.new(spec_transmission_add_node.outputs["Bxdf"], transmission_coat_add_node.inputs["material1"]) - nt.links.new(coat_node.outputs["Bxdf"], transmission_coat_add_node.inputs["material2"]) + nt.links.new(spec_transmission_add_node.outputs["bxdf_out"], transmission_coat_add_node.inputs["material1"]) + nt.links.new(coat_node.outputs["bxdf_out"], transmission_coat_add_node.inputs["material2"]) nt.links.new(rman_node.outputs["out_clearcoat"], transmission_coat_add_node.inputs["weight2"]) # emission @@ -592,11 +694,11 @@ def convert_principled_bsdf_to_lama(nt, node, final_mix_node): emission_node.location[1] -= 240.0 nodes_list.append(emission_node) - nt.links.new(rman_node.outputs["out_emissionColor"], emission_node.inputs["color"]) + nt.links.new(rman_node.outputs["out_emissionColor"], emission_node.inputs["emissionColor"]) # final mix node - nt.links.new(transmission_coat_add_node.outputs["Bxdf"], final_mix_node.inputs["material1"]) - nt.links.new(emission_node.outputs["Bxdf"], final_mix_node.inputs["material2"]) + nt.links.new(transmission_coat_add_node.outputs["bxdf_out"], final_mix_node.inputs["material1"]) + nt.links.new(emission_node.outputs["bxdf_out"], final_mix_node.inputs["material2"]) nt.links.new(rman_node.outputs["out_emissionMix"], final_mix_node.inputs["mix"]) # close ui_open connections @@ -610,10 +712,10 @@ def convert_diffuse_bsdf(nt, node, rman_node): inputs = node.inputs rman_node.name = 'diffuse_bsdf' - convert_cycles_input(nt, inputs['Color'], rman_node, "color") + convert_cycles_input(nt, inputs['Color'], rman_node, "diffuseColor") convert_cycles_input(nt, inputs['Roughness'], rman_node, "roughness") - convert_cycles_input(nt, inputs['Normal'], rman_node, "normal") + convert_cycles_input(nt, inputs['Normal'], rman_node, "diffuseNormal") def convert_glossy_bsdf(nt, node, rman_node): inputs = node.inputs @@ -623,7 +725,7 @@ def convert_glossy_bsdf(nt, node, rman_node): convert_cycles_input(nt, inputs['Roughness'], rman_node, "roughness") convert_cycles_input( - nt, inputs['Normal'], rman_node, "normal") + nt, inputs['Normal'], rman_node, "conductorNormal") if type(node).__class__ == 'ShaderNodeBsdfAnisotropic': convert_cycles_input( @@ -639,7 +741,7 @@ def convert_glass_bsdf(nt, node, rman_node): rman_node, "roughness") convert_cycles_input(nt, inputs['IOR'], rman_node, "IOR") - convert_cycles_input(nt, inputs['Normal'], rman_node, "normal") + convert_cycles_input(nt, inputs['Normal'], rman_node, "dielectricNormal") def convert_refraction_bsdf(nt, node, rman_node): @@ -651,7 +753,7 @@ def convert_refraction_bsdf(nt, node, rman_node): rman_node, "roughness") convert_cycles_input(nt, inputs['IOR'], rman_node, "IOR") - convert_cycles_input(nt, inputs['Normal'], rman_node, "normal") + convert_cycles_input(nt, inputs['Normal'], rman_node, "dielectricNormal") def convert_transparent_bsdf(nt, node, rman_node): @@ -664,35 +766,35 @@ def convert_transparent_bsdf(nt, node, rman_node): def convert_translucent_bsdf(nt, node, rman_node): inputs = node.inputs convert_cycles_input(nt, inputs['Color'], rman_node, "reflectionTint") - convert_cycles_input(nt, inputs['Normal'], rman_node, "normal") + convert_cycles_input(nt, inputs['Normal'], rman_node, "translucentNormal") def convert_sss_bsdf(nt, node, rman_node): inputs = node.inputs rman_node.name = 'sss_bsdf' - convert_cycles_input(nt, inputs['Color'], rman_node, "color") + convert_cycles_input(nt, inputs['Color'], rman_node, "sssColor") convert_cycles_input(nt, inputs['Radius'], rman_node, "radius") convert_cycles_input(nt, inputs['Scale'], rman_node, "scale") convert_cycles_input(nt, inputs['IOR'], rman_node, "IOR") - convert_cycles_input(nt, inputs['Normal'], rman_node, "normal") + convert_cycles_input(nt, inputs['Normal'], rman_node, "sssNormal") def convert_velvet_bsdf(nt, node, rman_node): inputs = node.inputs rman_node.name = 'velvet_bsdf' - convert_cycles_input(nt, inputs['Color'], rman_node, "color") - convert_cycles_input(nt, inputs['Normal'], rman_node, "normal") + convert_cycles_input(nt, inputs['Color'], rman_node, "sheenColor") + convert_cycles_input(nt, inputs['Normal'], rman_node, "sheenNormal") def convert_emission_bsdf(nt, node, rman_node): inputs = node.inputs rman_node.name = 'emission_bsdf' - convert_cycles_input(nt, inputs['Color'], rman_node, "color") + convert_cycles_input(nt, inputs['Color'], rman_node, "emissionColor") if not node.inputs['Color'].is_linked and not node.inputs['Strength']: - emission_color = getattr(rman_node, 'color') + emission_color = getattr(rman_node, 'emissionColor') emission_color = inputs['Strength'] * emission_color - setattr(rman_node, 'color', emission_color) + setattr(rman_node, 'emissionColor', emission_color) def convert_hair_bsdf(nt, node, rman_node): inputs = node.inputs @@ -774,5 +876,6 @@ def convert_volume_principled(nt, node, rman_node): 'ShaderNodeMath': ('', convert_math_node), 'ShaderNodeRGB': ('PxrHSL', convert_rgb_node), 'ShaderNodeValue': ('PxrToFloat', convert_node_value), - 'ShaderNodeAttribute': ('PxrPrimvar', convert_attribute_node) + 'ShaderNodeAttribute': ('PxrPrimvar', convert_attribute_node), + 'ShaderNodeWireframe': ('PxrWireframe', convert_wireframe_node) } diff --git a/rman_engine.py b/rman_engine.py new file mode 100644 index 00000000..1fdee647 --- /dev/null +++ b/rman_engine.py @@ -0,0 +1,245 @@ +import bpy +import bgl +import blf + +from .rfb_utils.prefs_utils import get_pref +from .rfb_utils import string_utils +from .rfb_utils import register_utils +from .rfb_logger import rfb_log + +class PRManRender(bpy.types.RenderEngine): + bl_idname = 'PRMAN_RENDER' + bl_label = "RenderMan" + bl_use_preview = False # Turn off preview renders + bl_use_save_buffers = True + bl_use_shading_nodes = True # We support shading nodes + bl_use_shading_nodes_custom = False + bl_use_eevee_viewport = True # Use Eevee for look dev viewport mode + bl_use_postprocess = True + + def __init__(self): + from . import rman_render + self.rman_render = rman_render.RmanRender.get_rman_render() + self.export_failed = None + self.ipr_already_running = False + if self.rman_render.rman_interactive_running: + # If IPR is already running, just flag it + # and don't do anything in the update methods + self.ipr_already_running = True + return + + def __del__(self): + try: + from . import rman_render + except ModuleNotFoundError: + return + + rr = rman_render.RmanRender.get_rman_render() + try: + if self.is_preview: + # If this was a preview render, return + return + except: + pass + + if rr.rman_running: + if rr.rman_interactive_running: + rfb_log().debug("Stop interactive render.") + rr.rman_is_live_rendering = False + elif rr.is_regular_rendering(): + rfb_log().debug("Stop render.") + rr.stop_render(stop_draw_thread=False) + + def update(self, data, depsgraph): + pass + + def view_update(self, context, depsgraph): + ''' + For viewport renders. Blender calls view_update when starting viewport renders + and/or something changes in the scene. + ''' + + # check if we are already doing a regular render + if self.rman_render.is_regular_rendering(): + return + + if self.export_failed: + return + + if self.ipr_already_running: + return + + if self.rman_render.is_ipr_to_it(): + # if we are already IPRing to "it", stop the render + self.rman_render.stop_render(stop_draw_thread=False) + + # if interactive rendering has not started, start it + if not self.rman_render.rman_interactive_running and self.rman_render.sg_scene is None: + self.rman_render.bl_engine = self + self.rman_render.rman_scene.ipr_render_into = 'blender' + if not self.rman_render.start_interactive_render(context, depsgraph): + self.export_failed = True + return + self.export_failed = False + + if self.rman_render.rman_interactive_running and not self.rman_render.rman_license_failed: + self.rman_render.update_scene(context, depsgraph) + + def view_draw(self, context, depsgraph): + ''' + For viewport renders. Blender calls view_draw whenever it redraws the 3D viewport. + This is where we check for camera moves and draw pxiels from our + Blender display driver. + ''' + if self.export_failed: + return + if self.ipr_already_running: + self.draw_viewport_message(context, 'Multiple viewport rendering not supported.') + return + + if self.rman_render.rman_interactive_running and not self.rman_render.rman_license_failed: + self.rman_render.update_view(context, depsgraph) + + self._draw_pixels(context, depsgraph) + + def _increment_version_tokens(self, external_render=False): + bl_scene = bpy.context.scene + vi = get_pref('rman_scene_version_increment', default='MANUALLY') + ti = get_pref('rman_scene_take_increment', default='MANUALLY') + + if (vi == 'RENDER' and not external_render) or (vi == 'BATCH_RENDER' and external_render): + bl_scene.renderman.version_token += 1 + string_utils.set_var('version', bl_scene.renderman.version_token) + + if (ti == 'RENDER' and not external_render) or (ti == 'BATCH_RENDER' and external_render): + bl_scene.renderman.take_token += 1 + string_utils.set_var('take', bl_scene.renderman.take_token) + + def update_render_passes(self, scene=None, renderlayer=None): + # this method allows us to add our AOVs as ports to the RenderLayer node + # in the compositor. + + from .rfb_utils import display_utils + if self.is_preview: + return + + if self.rman_render.rman_render_into != 'blender': + return + + if self.ipr_already_running: + return + + self.rman_render.rman_scene.bl_scene = scene + dspy_dict = display_utils.get_dspy_dict(self.rman_render.rman_scene, include_holdouts=False) + self.register_pass(scene, renderlayer, "Combined", 4, "RGBA", 'COLOR') + for i, dspy_nm in enumerate(dspy_dict['displays'].keys()): + if i == 0: + continue + dspy = dspy_dict['displays'][dspy_nm] + dspy_chan = dspy['params']['displayChannels'][0] + chan_info = dspy_dict['channels'][dspy_chan] + chan_type = chan_info['channelType']['value'] + + if chan_type == 'color': + self.register_pass(scene, renderlayer, dspy_nm, 3, "RGB", 'COLOR') + elif chan_type in ['vector', 'normal', 'point']: + self.register_pass(scene, renderlayer, dspy_nm, 3, "XYZ", 'VECTOR') + else: + self.register_pass(scene, renderlayer, dspy_nm, 1, "Z", 'VALUE') + + def render(self, depsgraph): + ''' + Main render entry point. Blender calls this when doing final renders or preview renders. + ''' + + bl_scene = depsgraph.scene_eval + rm = bl_scene.renderman + baking = (rm.hider_type in ['BAKE', 'BAKE_BRICKMAP_SELECTED']) + + if self.rman_render.rman_interactive_running: + # report an error if a render is trying to start while IPR is running + if self.is_preview and get_pref('rman_do_preview_renders', False): + #self.report({'ERROR'}, 'Cannot start a preview render when IPR is running') + rfb_log().debug('Cannot start a preview render when IPR is running') + pass + elif not self.is_preview: + self.report({'ERROR'}, 'Cannot start a render when IPR is running') + return + elif self.is_preview: + # double check we're not already viewport rendering + if self.rman_render.rman_interactive_running: + if get_pref('rman_do_preview_renders', False): + rfb_log().error("Cannot preview render while viewport rendering.") + return + if not get_pref('rman_do_preview_renders', False): + # user has turned off preview renders, just load the placeholder image + self.rman_render.bl_scene = depsgraph.scene_eval + #self.rman_render._load_placeholder_image() + return + if self.rman_render.rman_swatch_render_running: + return + self.rman_render.bl_engine = self + self.rman_render.start_swatch_render(depsgraph) + elif baking: + self.rman_render.bl_engine = self + if rm.enable_external_rendering: + self.rman_render.start_external_bake_render(depsgraph) + elif not self.rman_render.start_bake_render(depsgraph, for_background=bpy.app.background): + return + elif rm.enable_external_rendering: + self.rman_render.bl_engine = self + self.rman_render.start_external_render(depsgraph) + self._increment_version_tokens(external_render=True) + else: + for_background = bpy.app.background + self.rman_render.bl_engine = self + if not self.rman_render.start_render(depsgraph, for_background=for_background): + return + if not for_background: + self._increment_version_tokens(external_render=False) + + def draw_viewport_message(self, context, msg): + w = context.region.width + + pos_x = w / 2 - 100 + pos_y = 20 + blf.enable(0, blf.SHADOW) + blf.shadow_offset(0, 1, -1) + blf.shadow(0, 5, 0.0, 0.0, 0.0, 0.8) + blf.size(0, 32, 36) + blf.position(0, pos_x, pos_y, 0) + blf.color(0, 1.0, 0.0, 0.0, 1.0) + blf.draw(0, "%s" % (msg)) + blf.disable(0, blf.SHADOW) + + def _draw_pixels(self, context, depsgraph): + + if self.rman_render.rman_license_failed: + self.draw_viewport_message(context, self.rman_render.rman_license_failed_message) + + if not self.rman_render.rman_is_viewport_rendering: + return + + scene = depsgraph.scene + w = context.region.width + h = context.region.height + + # Bind shader that converts from scene linear to display space, + bgl.glEnable(bgl.GL_BLEND) + bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA) + self.bind_display_space_shader(scene) + + self.rman_render.draw_pixels(w, h) + + self.unbind_display_space_shader() + bgl.glDisable(bgl.GL_BLEND) + +classes = [ + PRManRender, +] + +def register(): + register_utils.rman_register_classes(classes) + +def unregister(): + register_utils.rman_unregister_classes(classes) \ No newline at end of file diff --git a/rman_handlers/__init__.py b/rman_handlers/__init__.py index 5e2a9986..27772a49 100644 --- a/rman_handlers/__init__.py +++ b/rman_handlers/__init__.py @@ -1,17 +1,33 @@ +from ..rfb_logger import rfb_log from ..rfb_utils import texture_utils from ..rfb_utils import string_utils from ..rfb_utils import shadergraph_utils from ..rfb_utils import upgrade_utils -from ..rman_ui import rman_ui_light_handlers from bpy.app.handlers import persistent import bpy +import os +import re +import sys + + +__ORIGINAL_BL_FILEPATH__ = None +__ORIGINAL_BL_FILE_FORMAT__ = None +__BL_TMP_FILE__ = None +if sys.platform == ("win32"): + __BL_TMP_DIR__ = 'C:/tmp' +else: + __BL_TMP_DIR__ = '/tmp' @persistent def rman_load_post(bl_scene): + from ..rman_ui import rman_ui_light_handlers + from ..rfb_utils import scene_utils + string_utils.update_blender_tokens_cb(bl_scene) rman_ui_light_handlers.clear_gl_tex_cache(bl_scene) texture_utils.txmanager_load_cb(bl_scene) upgrade_utils.upgrade_scene(bl_scene) + scene_utils.add_global_vol_aggregate() @persistent def rman_save_pre(bl_scene): @@ -24,8 +40,79 @@ def rman_save_post(bl_scene): texture_utils.txmanager_pre_save_cb(bl_scene) @persistent -def depsgraph_update_post(bl_scene, depsgraph): - texture_utils.depsgraph_handler(bl_scene, depsgraph) +def frame_change_post(bl_scene): + # update frame number + string_utils.update_frame_token(bl_scene.frame_current) + +@persistent +def despgraph_post_handler(bl_scene, depsgraph): + for update in depsgraph.updates: + texture_utils.depsgraph_handler(update, depsgraph) + +@persistent +def render_pre(bl_scene): + ''' + render_pre handler that changes the Blender filepath attribute + to match our filename output format. In the case of background + mode, and use_bl_compositor is off, set it to a temporary filename. + The temporary filename will get removed in the render_post handler. + ''' + global __ORIGINAL_BL_FILEPATH__ + global __ORIGINAL_BL_FILE_FORMAT__ + global __BL_TMP_FILE__ + global __BL_TMP_DIR__ + from ..rfb_utils import display_utils + from ..rfb_utils import scene_utils + + if bl_scene.render.engine != 'PRMAN_RENDER': + return + + __ORIGINAL_BL_FILEPATH__ = bl_scene.render.filepath + __ORIGINAL_BL_FILE_FORMAT__ = bl_scene.render.image_settings.file_format + write_comp = scene_utils.should_use_bl_compositor(bl_scene) + dspy_info = display_utils.get_beauty_filepath(bl_scene, use_blender_frame=True, expand_tokens=True, no_ext=True) + if display_utils.using_rman_displays(): + if write_comp: + bl_scene.render.filepath = dspy_info['filePath'] + img_format = display_utils.__RMAN_TO_BLENDER__.get(dspy_info['display_driver'], 'OPEN_EXR') + bl_scene.render.image_settings.file_format = img_format + else: + __BL_TMP_FILE__ = os.path.join(__BL_TMP_DIR__, '####.png') + bl_scene.render.filepath = __BL_TMP_FILE__ + bl_scene.render.image_settings.file_format = 'PNG' + else: + bl_scene.render.filepath = dspy_info['filePath'] + +@persistent +def render_post(bl_scene): + ''' + render_post handler that puts the Blender filepath attribute back + to its original value. Also, remove the temporary output file if + it exists. + ''' + + global __ORIGINAL_BL_FILEPATH__ + global __ORIGINAL_BL_FILE_FORMAT__ + global __BL_TMP_FILE__ + + if bl_scene.render.engine != 'PRMAN_RENDER': + return + + bl_scene.render.filepath = __ORIGINAL_BL_FILEPATH__ + bl_scene.render.image_settings.file_format = __ORIGINAL_BL_FILE_FORMAT__ + if __BL_TMP_FILE__: + filePath = re.sub(r'####', '%04d' % bl_scene.frame_current, __BL_TMP_FILE__) + if os.path.exists(filePath): + os.remove(filePath) + __BL_TMP_FILE__ = None + +@persistent +def render_stop(bl_scene): + from .. import rman_render + rr = rman_render.RmanRender.get_rman_render() + if rr.is_regular_rendering(): + rfb_log().debug("Render stop handler called. Try to stop the renderer.") + rr.stop_render() def register(): @@ -42,8 +129,24 @@ def register(): bpy.app.handlers.save_post.append(rman_save_post) # depsgraph_update_post handler - if depsgraph_update_post not in bpy.app.handlers.depsgraph_update_post: - bpy.app.handlers.depsgraph_update_post.append(depsgraph_update_post) + if despgraph_post_handler not in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.append(despgraph_post_handler) + + if frame_change_post not in bpy.app.handlers.frame_change_post: + bpy.app.handlers.frame_change_post.append(frame_change_post) + + if render_pre not in bpy.app.handlers.render_pre: + bpy.app.handlers.render_pre.append(render_pre) + + if render_post not in bpy.app.handlers.render_post: + bpy.app.handlers.render_post.append(render_post) + + if not bpy.app.background: + if render_stop not in bpy.app.handlers.render_complete: + bpy.app.handlers.render_complete.append(render_stop) + + if render_stop not in bpy.app.handlers.render_cancel: + bpy.app.handlers.render_cancel.append(render_stop) def unregister(): @@ -56,5 +159,23 @@ def unregister(): if rman_save_post in bpy.app.handlers.save_post: bpy.app.handlers.save_post.remove(rman_save_post) - if depsgraph_update_post in bpy.app.handlers.depsgraph_update_post: - bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update_post) + if despgraph_post_handler in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(despgraph_post_handler) + + if frame_change_post in bpy.app.handlers.frame_change_post: + bpy.app.handlers.frame_change_post.remove(frame_change_post) + + if render_pre in bpy.app.handlers.render_pre: + bpy.app.handlers.render_pre.remove(render_pre) + + if render_post in bpy.app.handlers.render_post: + bpy.app.handlers.render_post.remove(render_post) + + if render_stop in bpy.app.handlers.render_complete: + bpy.app.handlers.render_complete.remove(render_stop) + + if render_stop in bpy.app.handlers.render_cancel: + bpy.app.handlers.render_cancel.remove(render_stop) + + from . import rman_it_handlers + rman_it_handlers.remove_ipr_to_it_handlers() diff --git a/rman_handlers/rman_it_handlers.py b/rman_handlers/rman_it_handlers.py new file mode 100644 index 00000000..1b0b28d1 --- /dev/null +++ b/rman_handlers/rman_it_handlers.py @@ -0,0 +1,45 @@ +from bpy.app.handlers import persistent +import bpy + +@persistent +def ipr_it_depsgraph_update_post(bl_scene, depsgraph): + from ..rman_render import RmanRender + rman_render = RmanRender.get_rman_render() + + # check updates if we are render ipring into it + if rman_render.is_ipr_to_it(): + context = bpy.context + rman_render.update_scene(context, depsgraph) + rman_render.update_view(context, depsgraph) + + +@persistent +def ipr_frame_change_post(bl_scene): + from ..rman_render import RmanRender + rman_render = RmanRender.get_rman_render() + # check updates if we are render ipring into it + if rman_render.is_ipr_to_it(): + context = bpy.context + depsgraph = context.evaluated_depsgraph_get() + rman_render.update_scene(context, depsgraph) + rman_render.update_view(context, depsgraph) + +def add_ipr_to_it_handlers(): + ''' + This adds handlers needed when we ipr to it + ''' + if ipr_it_depsgraph_update_post not in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.append(ipr_it_depsgraph_update_post) + + if ipr_frame_change_post not in bpy.app.handlers.frame_change_post: + bpy.app.handlers.frame_change_post.append(ipr_frame_change_post) + +def remove_ipr_to_it_handlers(): + ''' + Remove handlers needed when we ipr to it + ''' + if ipr_it_depsgraph_update_post in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(ipr_it_depsgraph_update_post) + + if ipr_frame_change_post in bpy.app.handlers.frame_change_post: + bpy.app.handlers.frame_change_post.remove(ipr_frame_change_post) \ No newline at end of file diff --git a/rman_operators/rman_operators_collections.py b/rman_operators/rman_operators_collections.py index ee066c5a..fbcd7f02 100644 --- a/rman_operators/rman_operators_collections.py +++ b/rman_operators/rman_operators_collections.py @@ -3,6 +3,7 @@ from ..rfb_logger import rfb_log from ..rfb_utils import shadergraph_utils from ..rfb_utils import scenegraph_utils +from ..rfb_utils import object_utils from ..rfb_utils.rman_socket_utils import node_add_input import bpy @@ -53,7 +54,7 @@ def invoke(self, context, event): if self.properties.action == 'ADD': dflt_name = self.properties.defaultname collection.add() - index += 1 + index = len(collection)-1 setattr(rm, coll_idx, index) if dflt_name != '': for coll in collection: @@ -107,7 +108,7 @@ def invoke(self, context, event): if self.properties.action == 'ADD': dflt_name = self.properties.defaultname collection.add() - index += 1 + index = len(collection)-1 setattr(rm, coll_idx, index) i = 0 if dflt_name != '': @@ -163,7 +164,7 @@ def invoke(self, context, event): if self.properties.action == 'ADD': dflt_name = self.properties.defaultname collection.add() - index += 1 + index = len(collection)-1 setattr(rm, coll_idx, index) i = 0 if dflt_name != '': @@ -227,9 +228,12 @@ def invoke(self, context, event): if coll.name == dflt_name: dflt_name = '%s_NEW' % dflt_name collection.add() - index += 1 + index = len(collection)-1 setattr(rm, coll_idx, index) - collection[-1].name = dflt_name + try: + collection[-1].name = dflt_name + except: + pass elif self.properties.action == 'REMOVE': collection.remove(index) @@ -282,7 +286,7 @@ def invoke(self, context, event): if coll.name == dflt_name: dflt_name = '%s_NEW' % dflt_name collection.add() - index += 1 + index = len(collection)-1 setattr(rm, coll_idx, index) collection[-1].name = dflt_name @@ -631,7 +635,8 @@ def add_selected(self, context): if op: op.selected_light_name = '0' - light_ob.update_tag(refresh={'DATA'}) + if not object_utils.is_light_filter(light_ob): + light_ob.update_tag(refresh={'DATA'}) def add_scene_selected(self, context): scene = context.scene @@ -666,7 +671,8 @@ def add_scene_selected(self, context): ll = scene.renderman.light_links.add() ll.name = light_ob.name ll.light_ob = light_ob.data - light_ob.update_tag(refresh={'DATA'}) + if not object_utils.is_light_filter(light_ob): + light_ob.update_tag(refresh={'DATA'}) def execute(self, context): if self.properties.do_scene_selected: @@ -885,7 +891,7 @@ def execute(self, context): connectable = False if self.action == 'ADD': elem = collection.add() - index += 1 + index = len(collection)-1 setattr(node, self.collection_index, index) elem.name = '%s[%d]' % (self.param_name, len(collection)-1) elem.type = self.elem_type diff --git a/rman_operators/rman_operators_editors/rman_operators_editors_lightlink.py b/rman_operators/rman_operators_editors/rman_operators_editors_lightlink.py index 19603b10..738f7aeb 100644 --- a/rman_operators/rman_operators_editors/rman_operators_editors_lightlink.py +++ b/rman_operators/rman_operators_editors/rman_operators_editors_lightlink.py @@ -2,12 +2,383 @@ from ...rfb_utils import scene_utils from ...rfb_utils import shadergraph_utils +from ...rfb_utils import string_utils +from ...rfb_utils import scenegraph_utils from ...rfb_logger import rfb_log +from ...rfb_utils.prefs_utils import get_pref, using_qt, show_wip_qt +from ...rfb_utils import object_utils +from ...rfb_utils.envconfig_utils import envconfig from ... import rfb_icons from ...rman_operators.rman_operators_collections import return_empty_list from ...rman_config import __RFB_CONFIG_DICT__ as rfb_config +from ...rman_constants import RFB_ADDON_PATH import bpy +import os import re +import sys + + +__LIGHT_LINKING_WINDOW__ = None + +if not bpy.app.background: + from ...rman_ui import rfb_qt as rfb_qt + from PySide2 import QtCore, QtWidgets, QtGui + + class LightLinkingQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.light_linking_qt_app_timed" + bl_label = "Light Linking Editor" + + def __init__(self): + super(LightLinkingQtAppTimed, self).__init__() + + def execute(self, context): + self._window = LightLinkingQtWrapper() + return super(LightLinkingQtAppTimed, self).execute(context) + + class StandardItem(QtGui.QStandardItem): + def __init__(self, txt=''): + super().__init__() + self.setEditable(False) + self.setText(txt) + + class LightLinkingQtWrapper(rfb_qt.RmanQtWrapper): + def __init__(self) -> None: + super(LightLinkingQtWrapper, self).__init__() + self.setObjectName("Dialog") + self.resize(825, 526) + self.buttonBox = QtWidgets.QDialogButtonBox(self) + self.buttonBox.setGeometry(QtCore.QRect(620, 450, 166, 24)) + + # hide OK, and cancel buttons + #self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + + self.buttonBox.setObjectName("buttonBox") + #self.invert_checkBox = QtWidgets.QCheckBox(self) + #self.invert_checkBox.setGeometry(QtCore.QRect(730, 30, 85, 21)) + #self.invert_checkBox.setObjectName("invert_checkBox") + self.widget = QtWidgets.QWidget(self) + self.widget.setGeometry(QtCore.QRect(40, 70, 751, 361)) + self.widget.setObjectName("widget") + self.gridLayout = QtWidgets.QGridLayout(self.widget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setHorizontalSpacing(50) + self.gridLayout.setObjectName("gridLayout") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.lights_label = QtWidgets.QLabel(self.widget) + self.lights_label.setObjectName("lights_label") + self.verticalLayout.addWidget(self.lights_label) + self.lights_treeView = QtWidgets.QListWidget(self) + self.lights_treeView.setObjectName("lights_treeView") + self.verticalLayout.addWidget(self.lights_treeView) + self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.objects_label = QtWidgets.QLabel(self.widget) + self.objects_label.setObjectName("objects_label") + self.verticalLayout_2.addWidget(self.objects_label) + + self.objects_treeView = QtWidgets.QTreeView(self.widget) + self.objects_treeView.setSelectionMode( + QtWidgets.QAbstractItemView.MultiSelection + ) + self.objects_treeView.setObjectName("objects_treeView") + self.objects_treeView.setHeaderHidden(True) + self.treeModel = QtGui.QStandardItemModel(self) + self.rootNode = self.treeModel.invisibleRootItem() + self.objects_treeView.setModel(self.treeModel) + + self.verticalLayout_2.addWidget(self.objects_treeView) + self.gridLayout.addLayout(self.verticalLayout_2, 0, 1, 1, 1) + + self.lights_treeView.itemSelectionChanged.connect(self.lights_index_changed) + + self.objects_treeView.selectionModel().selectionChanged.connect(self.linked_objects_selection) + + self.light_link_item = None + self.total_objects = 0 + self.retranslateUi() + self.refresh_lights() + + self.add_handlers() + + + def retranslateUi(self): + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("Dialog", "Light Linking")) + #self.invert_checkBox.setToolTip(_translate("Dialog", "Invert light linking")) + #self.invert_checkBox.setText(_translate("Dialog", "Invert")) + self.lights_label.setText(_translate("Dialog", "Lights")) + self.objects_label.setText(_translate("Dialog", "Objects")) + + def closeEvent(self, event): + self.remove_handlers() + super(LightLinkingQtWrapper, self).closeEvent(event) + + def add_handlers(self): + if self.depsgraph_update_post not in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.append(self.depsgraph_update_post) + + def remove_handlers(self): + if self.depsgraph_update_post in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(self.depsgraph_update_post) + + def depsgraph_update_post(self, bl_scene, depsgraph): + for dps_update in reversed(depsgraph.updates): + if isinstance(dps_update.id, bpy.types.Collection): + self.refresh_lights() + self.refresh_linked_objects() + #self.lights_index_changed() + #elif isinstance(dps_update.id, bpy.types.Scene): + # self.refresh_lights() + + def update(self): + super(LightLinkingQtWrapper, self).update() + + + def refresh_lights(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + all_lights = [l.name for l in scene_utils.get_all_lights(scene, include_light_filters=True)] + remove_items = [] + + for i in range(self.lights_treeView.count()): + item = self.lights_treeView.item(i) + name = item.text() + if name not in all_lights: + remove_items.append(item) + else: + all_lights.remove(name) + + for nm in all_lights: + item = QtWidgets.QListWidgetItem(nm) + ob = scene.objects[nm] + light_shader_name = ob.data.renderman.get_light_node_name() + icon_path = os.path.join(RFB_ADDON_PATH, 'rfb_icons/out_%s.png' % light_shader_name) + if os.path.exists(icon_path): + icon = QtGui.QIcon(icon_path) + item.setIcon(icon) + item.setFlags(item.flags()) + self.lights_treeView.addItem(item) + + for item in remove_items: + self.lights_treeView.takeItem(self.lights_treeView.row(item)) + del item + + if remove_items or len(all_lights) > 0: + self.lights_treeView.setCurrentRow(-1) + + def find_light_link_item(self, light_nm=''): + context = bpy.context + scene = context.scene + rm = scene.renderman + light_link_item = None + for ll in rm.light_links: + if ll.light_ob.name == light_nm: + light_link_item = ll + break + return light_link_item + + def get_all_object_items(self, standard_item, items): + for i in range(standard_item.rowCount()): + item = standard_item.child(i) + items.append(item) + if item.rowCount() > 0: + self.get_all_object_items(item, items) + + def bl_select_objects(self, obs): + context = bpy.context + for ob in context.selected_objects: + ob.select_set(False) + for ob in obs: + ob.select_set(True) + context.view_layer.objects.active = ob + + def lights_index_changed(self): + idx = int(self.lights_treeView.currentRow()) + current_item = self.lights_treeView.currentItem() + if not current_item: + self.treeModel.clear() + self.objects_treeView.selectionModel().select(QtCore.QItemSelection(), QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.NoUpdate) + return + self.rootNode = self.treeModel.invisibleRootItem() + if self.rootNode.rowCount() == 0: + self.refresh_linked_objects() + context = bpy.context + scene = context.scene + rm = scene.renderman + rm.light_links_index = idx + light_nm = current_item.text() + light_ob = context.scene.objects.get(light_nm, None) + if light_ob: + self.bl_select_objects([light_ob]) + + light_link_item = self.find_light_link_item(light_nm) + selected_items = QtCore.QItemSelection() + items = [] + self.get_all_object_items(self.rootNode, items) + + if light_link_item is None: + ''' + if not object_utils.is_light_filter(light_ob): + light_link_item = scene.renderman.light_links.add() + light_link_item.name = light_ob.name + light_link_item.light_ob = light_ob + ''' + + for item in items: + idx = self.treeModel.indexFromItem(item) + selection_range = QtCore.QItemSelectionRange(idx) + selected_items.append(selection_range) + else: + for item in items: + idx = self.treeModel.indexFromItem(item) + ob_nm = item.text() + found = False + for member in light_link_item.members: + ob = member.ob_pointer + if ob is None: + continue + if ob.name == ob_nm: + found = True + break + + if found: + continue + + selection_range = QtCore.QItemSelectionRange(idx) + selected_items.append(selection_range) + self.objects_treeView.selectionModel().select(selected_items, QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.NoUpdate) + + def find_item(self, standard_item, ob): + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + if not item: + continue + if item.text() == ob.name: + return item + if item.rowCount() > 0: + return self.find_item(item, ob) + + return None + + def get_all_removed_items(self, standard_item, scene, remove_items): + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + if not item: + continue + nm = item.text() + if nm not in scene.objects: + remove_items.append(item) + if item.rowCount() > 0: + self.get_all_removed_items(item, scene, remove_items) + + + def refresh_linked_objects(self): + context = bpy.context + scene = context.scene + self.rootNode = self.treeModel.invisibleRootItem() + + def add_children(root_item, ob): + for child in ob.children: + if child.type in ['CAMERA', 'LIGHT', 'ARMATURE']: + continue + item = self.find_item(root_item, child) + if not item: + item = StandardItem(txt=child.name) + self.total_objects += 1 + root_item.appendRow(item) + if len(child.children) > 0: + add_children(item, child) + + remove_items = [] + self.get_all_removed_items(self.rootNode, scene, remove_items) + + for item in remove_items: + self.treeModel.takeRow(item.row()) + self.total_objects -= 1 + del item + + root_parents = [ob for ob in scene.objects if ob.parent is None] + for ob in root_parents: + if ob.type in ['CAMERA', 'LIGHT', 'ARMATURE']: + continue + item = self.find_item(self.rootNode, ob) + if not item: + item = StandardItem(txt=ob.name) + self.total_objects += 1 + self.rootNode.appendRow(item) + if len(ob.children) > 0: + add_children(item, ob) + + self.objects_treeView.expandAll() + + def linked_objects_selection(self, selected, deselected): + idx = int(self.lights_treeView.currentRow()) + current_item = self.lights_treeView.currentItem() + if not current_item: + return + context = bpy.context + scene = context.scene + rm = scene.renderman + light_nm = current_item.text() + light_ob = context.scene.objects.get(light_nm, None) + light_props = shadergraph_utils.get_rman_light_properties_group(light_ob.original) + is_light_filter = light_props.renderman_light_role == 'RMAN_LIGHTFILTER' + ll = self.find_light_link_item(light_nm) + + if ll is None: + ll = scene.renderman.light_links.add() + ll.name = light_ob.name + ll.light_ob = light_ob + rm.lights_link_index = len(rm.light_links)-1 + + if is_light_filter: + # linkingGroups should only be set if one of the items is deselected + total_selected_items = len(self.objects_treeView.selectionModel().selectedIndexes()) + + if total_selected_items == self.total_objects and light_props.linkingGroups != "": + light_props.linkingGroups = "" + light_ob.update_tag(refresh={'DATA'}) + elif total_selected_items != self.total_objects and light_props.linkingGroups == "": + light_props.linkingGroups = string_utils.sanitize_node_name(light_ob.name_full) + light_ob.update_tag(refresh={'DATA'}) + + for i in deselected.indexes(): + item = self.objects_treeView.model().itemFromIndex(i) + ob = bpy.data.objects.get(item.text(), None) + if ob is None: + continue + do_add = True + for member in ll.members: + if ob == member.ob_pointer: + do_add = False + break + + if do_add: + member = ll.members.add() + member.name = ob.name + member.ob_pointer = ob + member.illuminate = 'OFF' + scene_utils.set_lightlinking_properties(ob, light_ob, member.illuminate) + + for i in selected.indexes(): + item = self.objects_treeView.model().itemFromIndex(i) + ob = bpy.data.objects.get(item.text(), None) + if ob is None: + continue + do_remove = False + idx = -1 + for i, member in enumerate(ll.members): + if ob == member.ob_pointer: + do_remove = True + idx = i + break + if do_remove: + member = ll.members.remove(idx) + scene_utils.set_lightlinking_properties(ob, light_ob, '') class RENDERMAN_UL_LightLink_Light_List(bpy.types.UIList): @@ -276,6 +647,13 @@ def check_light_links(self, context): if lg.light_ob is None or lg.light_ob.name not in scene.objects: delete_any.insert(0, i) continue + + if object_utils.is_light_filter(lg.light_ob): + if lg.light_ob.data.renderman.linkingGroups == "": + lg.light_ob.data.renderman.linkingGroups = string_utils.sanitize_node_name(lg.light_ob.name_full) + else: + lg.light_ob.data.renderman.linkingGroups = "" + delete_objs = [] for j in range(len(lg.members)-1, -1, -1): member = lg.members[j] @@ -290,6 +668,16 @@ def check_light_links(self, context): rm.light_links_index -= 1 def invoke(self, context, event): + + if using_qt() and show_wip_qt(): + global __LIGHT_LINKING_WINDOW__ + if sys.platform == "darwin": + rfb_qt.run_with_timer(__LIGHT_LINKING_WINDOW__, LightLinkingQtWrapper) + else: + bpy.ops.wm.light_linking_qt_app_timed() + + return {'FINISHED'} + wm = context.window_manager width = rfb_config['editor_preferences']['lightlink_editor']['width'] self.event = event @@ -299,9 +687,12 @@ def invoke(self, context, event): classes = [ PRMAN_PT_Renderman_Open_Light_Linking, RENDERMAN_UL_LightLink_Light_List, - RENDERMAN_UL_LightLink_Object_List + RENDERMAN_UL_LightLink_Object_List, ] +if not bpy.app.background: + classes.append(LightLinkingQtAppTimed) + def register(): from ...rfb_utils import register_utils diff --git a/rman_operators/rman_operators_editors/rman_operators_editors_lightmixer.py b/rman_operators/rman_operators_editors/rman_operators_editors_lightmixer.py index 6c054939..d505ed48 100644 --- a/rman_operators/rman_operators_editors/rman_operators_editors_lightmixer.py +++ b/rman_operators/rman_operators_editors/rman_operators_editors_lightmixer.py @@ -5,10 +5,647 @@ from ...rfb_utils import shadergraph_utils from ...rfb_logger import rfb_log from ... import rfb_icons +from ...rfb_utils.prefs_utils import using_qt, show_wip_qt from ...rman_operators.rman_operators_collections import return_empty_list from ...rman_config import __RFB_CONFIG_DICT__ as rfb_config import bpy import re +import sys + +__LIGHT_MIXER_WINDOW__ = None + +if not bpy.app.background: + from ...rman_ui import rfb_qt as rfb_qt + from PySide2 import QtCore, QtWidgets, QtGui + + class LightMixerQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.light_mixer_qt_app_timed" + bl_label = "RenderMan Trace Sets Editor" + + def __init__(self): + super(LightMixerQtAppTimed, self).__init__() + + def execute(self, context): + self._window = LightMixerQtWrapper() + return super(LightMixerQtAppTimed, self).execute(context) + + class StandardItem(QtGui.QStandardItem): + def __init__(self, txt=''): + super().__init__() + self.setEditable(False) + self.setText(txt) + + class ParamLabel(QtWidgets.QLabel): + def __init__(self, *args, **kwargs): + super(ParamLabel, self).__init__(*args, **kwargs) + #self.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + class VBoxLayout(QtWidgets.QVBoxLayout): + def __init__(self, m=1, s=1): + super(VBoxLayout, self).__init__() + self.setContentsMargins(m, m, m, m) + self.setSpacing(s) + + + class HBoxLayout(QtWidgets.QHBoxLayout): + def __init__(self, m=5, s=10): + super(HBoxLayout, self).__init__() + self.setContentsMargins(m, m, m, m) + self.setSpacing(s) + + class SliderParam(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(SliderParam, self).__init__(parent=kwargs.get("parent", None)) + self.light_shader = None + + # build ui + self._lyt = HBoxLayout() + self.light_shader = kwargs.get("light_shader", None) + self.param = kwargs.get("param", "") + self.param_label = ParamLabel(kwargs.get("label", "label")) + self._lyt.addWidget(self.param_label) + self.sl = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) + self.sl.setMinimum(kwargs.get("min", 0.0)) + self.sl.setMaximum(kwargs.get("max", 10.0)) + self.sl.setValue(kwargs.get("value", 0.0)) + self.sl.setTickPosition(QtWidgets.QSlider.TicksBothSides) + self.sl.setTickInterval(1.0) + self.sl.setSingleStep(kwargs.get("step", 0.1)) + self.sl.valueChanged.connect(self.slider_changed) + self._lyt.addWidget(self.sl) + + self._field = QtWidgets.QDoubleSpinBox() + self._field.setMinimum(0.0) + self._field.setMaximum(100000.0) + self._field.setSingleStep(kwargs.get("step", 0.1)) + self._field.setValue(kwargs.get("value", 0.0)) + self._field.valueChanged.connect(self.value_changed) + #self.setToolTip(kwargs.get("tooltip", "")) + self._lyt.addWidget(self._field) + self.setLayout(self._lyt) + + def value_changed(self, val): + val = self._field.value() + #self.sl.setValue(val) + self.update_shader(val) + + def slider_changed(self): + val = self.sl.value() + self._field.setValue(val) + self.update_shader(val) + + def update_shader(self, val): + if self.light_shader: + setattr(self.light_shader, self.param, val) + + class FloatParam(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(FloatParam, self).__init__(parent=kwargs.get("parent", None)) + # build ui + self._lyt = HBoxLayout() + self.light_shader = kwargs.get("light_shader", None) + self.param = kwargs.get("param", "") + self.param_label = ParamLabel(kwargs.get("label", "label")) + self._lyt.addWidget(self.param_label) + self._field = QtWidgets.QDoubleSpinBox() + self._field.setMinimum(kwargs.get("min", 0.0)) + self._field.setMaximum(kwargs.get("max", 1.0)) + self._field.setSingleStep(kwargs.get("step", 0.1)) + self._field.setValue(kwargs.get("value", 0.0)) + self.setToolTip(kwargs.get("tooltip", "")) + self._lyt.addWidget(self._field) + self.setLayout(self._lyt) + # change cb + self._field.valueChanged.connect(self.on_change) + + @property + def value(self): + return self._field.value + + def on_change(self, val): + setattr(self.light_shader, self.param, val) + + class BoolParam(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(BoolParam, self).__init__(parent=kwargs.get("parent", None)) + self.light_shader = kwargs.get("light_shader", None) + self.param = kwargs.get("param", "") + self.param_label = ParamLabel(kwargs.get("label", "label")) + self._lyt = HBoxLayout(m=1, s=1) + self._lyt.addWidget(self.param_label) + self._cb = QtWidgets.QCheckBox() + self._cb.setTristate(False) + self._cb.setChecked(kwargs.get("value", False)) + self.setToolTip(kwargs.get("tooltip", "")) + self._lyt.addWidget(self._cb) + self.setLayout(self._lyt) + # change cb + self._cb.stateChanged.connect(self.on_change) + + @property + def value(self): + return self._cb.isChecked() + + def setChecked(self, v): + self._cb.setChecked(v) + + def on_change(self, val): + setattr(self.light_shader, self.param, bool(val)) + + class ColorButton(QtWidgets.QWidget): + colorChanged = QtCore.Signal(object) + + def __init__(self, *args, **kwargs): + super(ColorButton, self).__init__(parent=kwargs.get("parent", None)) + + self._lyt = HBoxLayout() + self.light_shader = kwargs.get("light_shader", None) + self.param = kwargs.get("param", "") + self.param_label = ParamLabel(kwargs.get("label", "label")) + self._lyt.addWidget(self.param_label) + self._color = None + clr = kwargs.get("color", (0.0, 0.0, 0.0)) + self._default = QtGui.QColor.fromRgbF(clr[0], clr[1], clr[2]) + self.color_btn = QtWidgets.QPushButton() + self.color_btn.pressed.connect(self.onColorPicker) + self._lyt.addWidget(self.color_btn) + self.dlg = None + + self.setLayout(self._lyt) + + self._color = self._default + self.color_btn.setStyleSheet("background-color: %s;" % self._color.name()) + + def setColor(self, qcolor): + if qcolor != self._color: + self._color = qcolor + self.colorChanged.emit(qcolor) + + if self._color: + self.color_btn.setStyleSheet("background-color: %s;" % qcolor.name()) + else: + self.color_btn.setStyleSheet("") + + if self.light_shader: + setattr(self.light_shader, self.param, (qcolor.redF(), qcolor.greenF(), qcolor.blueF())) + + def color(self): + return self._color + + def onColorPicker(self): + if self.dlg is None: + self.dlg = QtWidgets.QColorDialog(self) + self.dlg.setOption(QtWidgets.QColorDialog.DontUseNativeDialog) + self.dlg.currentColorChanged.connect(self.currentColorChanged) + self.dlg.accepted.connect(self.dlg_accept) + self.dlg.rejected.connect(self.dlg_rejected) + + self.dlg.setCurrentColor(self._color) + self.dlg.open() + + def dlg_accept(self): + self.setColor(self.dlg.currentColor()) + + def dlg_rejected(self): + self.setColor(self._color) + + def currentColorChanged(self, qcolor): + if self.light_shader: + setattr(self.light_shader, self.param, (qcolor.redF(), qcolor.greenF(), qcolor.blueF())) + + def mousePressEvent(self, e): + if e.button() == QtCore.Qt.RightButton: + self.setColor(self._default) + + return super(ColorButton, self).mousePressEvent(e) + + class LightMixerWidget(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(LightMixerWidget, self).__init__(parent=kwargs.get("parent", None)) + + self._lyt = VBoxLayout(m=5) + self._lyt.setAlignment(QtCore.Qt.AlignTop) + self._lbl = QtWidgets.QLabel() + self._lbl.setAlignment(QtCore.Qt.AlignHCenter) + lbl = kwargs.get("name", "Light") + self._lbl.setText(lbl) + self._lyt.addWidget(self._lbl) + self.setLayout(self._lyt) + + def __del__(self): + self.remove_widgets() + + def add_widget(self, wgt): + self._lyt.addWidget(wgt) + + def remove_widgets(self): + for i in reversed(range(self._lyt.count())): + w = self._lyt.takeAt(i).widget() + if w is not None: + w.setParent(None) + w.deleteLater() + + class LightMixerLayout(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(LightMixerLayout, self).__init__(parent=kwargs.get("parent", None)) + + self.groupbox = QtWidgets.QGroupBox('') + self._gb_lyt = VBoxLayout() + self.groupbox.setLayout(self._gb_lyt) + scroll = QtWidgets.QScrollArea() + scroll.setWidget(self.groupbox) + scroll.setWidgetResizable(True) + self._lyt = VBoxLayout() + self._lyt.addWidget(scroll) + self.setLayout(self._lyt) + + def add_widget(self, wgt): + self._gb_lyt.addWidget(wgt) + + def remove_widgets(self): + for i in reversed(range(self._gb_lyt.count())): + w = self._gb_lyt.takeAt(i).widget() + if w is not None: + w.setParent(None) + w.deleteLater() + + class LightMixerQtWrapper(rfb_qt.RmanQtWrapper): + def __init__(self) -> None: + super(LightMixerQtWrapper, self).__init__() + + self.setWindowTitle('RenderMan Light Mixer') + self.resize(1100, 500) + self.buttonBox = QtWidgets.QDialogButtonBox(self) + self.buttonBox.setGeometry(QtCore.QRect(260, 440, 341, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + + # hide OK and cancel buttons + #self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + + self.buttonBox.setObjectName("buttonBox") + self.addButton = QtWidgets.QPushButton(self) + self.addButton.setGeometry(QtCore.QRect(280, 30, 31, 26)) + self.addButton.setObjectName("addButton") + self.addButton.setText("+") + self.addButton.setAutoDefault(False) + self.removeButton = QtWidgets.QPushButton(self) + self.removeButton.setGeometry(QtCore.QRect(280, 50, 31, 26)) + self.removeButton.setObjectName("removeButton") + self.removeButton.setText("-") + self.removeButton.setAutoDefault(False) + + self.mixerGroupObjects = QtWidgets.QTreeView(self) + self.mixerGroupObjects.setHeaderHidden(True) + self.treeModel = QtGui.QStandardItemModel(self) + self.rootNode = self.treeModel.invisibleRootItem() + self.mixerGroupObjects.setModel(self.treeModel) + + self.mixerGroupObjects.setGeometry(QtCore.QRect(30, 140, 250, 350)) + self.mixerGroupObjects.setObjectName("mixerGroupObjects") + self.mixerGroupObjects.setSelectionMode( + QtWidgets.QAbstractItemView.SingleSelection + ) + + self.mixerGroups = QtWidgets.QListWidget(self) + self.mixerGroups.setGeometry(QtCore.QRect(30, 30, 256, 80)) + self.mixerGroups.setObjectName("mixerGroups") + + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(40, 10, 200, 17)) + self.label.setText("Light Mixer Groups") + + self.label_2 = QtWidgets.QLabel(self) + self.label_2.setGeometry(QtCore.QRect(40, 120, 200, 17)) + self.label_2.setText("Lights") + + self.add_light_btn = QtWidgets.QPushButton(self) + self.add_light_btn.setGeometry(QtCore.QRect(279, 140, 31, 26)) + self.add_light_btn.setText("+") + self.add_light_btn.setToolTip("""Add selected lights to this mixer group""" ) + self.add_light_btn.setEnabled(False) + self.add_light_btn.setAutoDefault(False) + + self.remove_light_btn = QtWidgets.QPushButton(self) + self.remove_light_btn.setGeometry(QtCore.QRect(279, 160, 31, 26)) + self.remove_light_btn.setText("-") + self.remove_light_btn.setToolTip("""Remove selected lights""" ) + self.remove_light_btn.setEnabled(False) + self.remove_light_btn.setAutoDefault(False) + + self.addButton.clicked.connect(self.add_group) + self.removeButton.clicked.connect(self.remove_group) + self.add_light_btn.clicked.connect(self.add_light) + self.remove_light_btn.clicked.connect(self.remove_light) + + self.mixerGroups.itemChanged.connect(self.mixer_group_changed) + self.mixerGroups.itemSelectionChanged.connect(self.mixer_groups_index_changed) + self.mixerGroupObjects.selectionModel().selectionChanged.connect(self.mixer_group_objects_selection) + + self.light_mixer_wgt = LightMixerLayout(parent=self) + self.light_mixer_wgt.setGeometry(QtCore.QRect(340, 30, 600, 400)) + + self.refresh_groups() + self.mixerGroupObjects.expandAll() + self.enableAddLightButton() + + self.add_handlers() + + def closeEvent(self, event): + self.remove_handlers() + super(LightMixerQtWrapper, self).closeEvent(event) + + def add_handlers(self): + if self.depsgraph_update_post not in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.append(self.depsgraph_update_post) + + def remove_handlers(self): + if self.depsgraph_update_post in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(self.depsgraph_update_post) + + def depsgraph_update_post(self, bl_scene, depsgraph): + for dps_update in reversed(depsgraph.updates): + if isinstance(dps_update.id, bpy.types.Collection): + self.refresh_group_objects() + elif isinstance(dps_update.id, bpy.types.Scene): + self.enableAddLightButton() + + def enableAddLightButton(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + obs = getattr(context, 'selected_objects', None) + if obs is None: + obs = context.view_layer.objects.selected + lights = [ob for ob in obs if ob.type == "LIGHT"] + if len(lights) > 0: + any_lights = [] + if rm.light_mixer_groups_index > 0: + grp = rm.light_mixer_groups[rm.light_mixer_groups_index] + for ob in lights: + do_add = True + for member in grp.members: + if member.light_ob == ob: + do_add = False + break + if do_add: + any_lights.append(ob) + + self.add_light_btn.setEnabled(len(any_lights) > 0) + return + self.add_light_btn.setEnabled(True) + return + self.add_light_btn.setEnabled(False) + + def add_light(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + grp = rm.light_mixer_groups[rm.light_mixer_groups_index] + obs = getattr(context, 'selected_objects', None) + if obs is None: + obs = context.view_layer.objects.selected + for ob in obs: + do_add = True + for member in grp.members: + if member.light_ob == ob: + do_add = False + break + if do_add: + ob_in_group = grp.members.add() + ob_in_group.name = ob.name + ob_in_group.light_ob = ob + + self.refresh_group_objects() + + def remove_light(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + grp = rm.light_mixer_groups[rm.light_mixer_groups_index] + + index = self.mixerGroupObjects.selectedIndexes()[0] + item = index.model().itemFromIndex(index) + light_nm = item.text() + ob = context.scene.objects.get(light_nm, None) + if not ob: + return + + do_remove = False + idx = -1 + for i, member in enumerate(grp.members): + if ob == member.light_ob: + do_remove = True + idx = i + break + if do_remove: + member = grp.members.remove(idx) + + self.refresh_group_objects() + + + def update(self): + idx = int(self.mixerGroups.currentRow()) + self.addButton.setEnabled(True) + + super(LightMixerQtWrapper, self).update() + + def refresh_groups(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + self.mixerGroups.clear() + for grp in rm.light_mixer_groups: + item = QtWidgets.QListWidgetItem(grp.name) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + self.mixerGroups.addItem(item) + + if self.mixerGroups.count() > 0: + self.mixerGroups.setCurrentRow(rm.light_mixer_groups_index) + + def add_group(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + grp = rm.light_mixer_groups.add() + grp.name = 'mixerGroup_%d' % len(rm.light_mixer_groups) + rm.light_mixer_groups_index = len(rm.light_mixer_groups)-1 + self.refresh_groups() + + def remove_group(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + index = rm.object_groups_index + group = rm.object_groups[index] + # get a list of all objects in this group + ob_list = [member.ob_pointer for member in group.members] + rm.object_groups.remove(index) + rm.object_groups_index -= 1 + + # now tell each object to update + for ob in ob_list: + ob.update_tag(refresh={'OBJECT'}) + + self.refresh_groups() + + def mixer_group_changed(self, item): + idx = int(self.mixerGroups.currentRow()) + + context = bpy.context + scene = context.scene + rm = scene.renderman + grp = rm.light_mixer_groups[idx] + grp.name = item.text() + self.label_2.setText("Objects (%s)" % item.text()) + for member in grp.members: + ob = member.light_ob + ob.update_tag(refresh={'OBJECT'}) + + def find_item(self, standard_item, ob): + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + if item.text() == ob.name: + return item + + return None + + def refresh_group_objects(self): + self.light_mixer_wgt.remove_widgets() + idx = int(self.mixerGroups.currentRow()) + if idx == -1: + self.label_2.setText("Objects (no group selected)") + + + context = bpy.context + scene = context.scene + rm = scene.renderman + + grp = rm.light_mixer_groups[rm.light_mixer_groups_index] + + self.treeModel.clear() + self.rootNode = self.treeModel.invisibleRootItem() + + for member in grp.members: + ob = member.light_ob + + light_shader = shadergraph_utils.get_light_node(ob) + item = StandardItem(txt=ob.name) + self.rootNode.appendRow(item) + + lgt_mixer_wgt = LightMixerWidget(parent=self, name=ob.name) + + if light_shader.bl_label == 'PxrPortalLight': + enableTemperature = BoolParam(parent=self, + param="enableTemperature", + label="Enable Temperature", + value=light_shader.enableTemperature, + light_shader=light_shader + ) + lgt_mixer_wgt.add_widget(enableTemperature) + + temperature = FloatParam(parent=self, + param="temperature", + label="Temperature", + min=1000.0, + max=50000.0, + value=light_shader.temperature, + light_shader=light_shader + ) + lgt_mixer_wgt.add_widget(temperature) + + wgt = SliderParam(parent=self, + light_shader=light_shader, + value=light_shader.intensityMult, + min=0.0, + max=10.0, + param="intensityMult", + label="Intensity Mult") + lgt_mixer_wgt.add_widget(wgt) + + + else: + exposure_wgt = SliderParam(parent=self, + light_shader=light_shader, + value=light_shader.exposure, + min=0.0, + max=10.0, + param="exposure", + label="Exposure") + lgt_mixer_wgt.add_widget(exposure_wgt) + + wgt = SliderParam(parent=self, + light_shader=light_shader, + value=light_shader.intensity, + min=0.0, + max=10.0, + param="intensity", + label="Intensity") + lgt_mixer_wgt.add_widget(wgt) + + if light_shader.bl_label == 'PxrEnvDayLight': + color_picker = ColorButton(parent=self, + color=light_shader.skyTint, + param="skyTint", + label="Sky Tint", + light_shader=light_shader + ) + lgt_mixer_wgt.add_widget(color_picker) + else: + enableTemperature = BoolParam(parent=self, + param="enableTemperature", + label="Enable Temperature", + value=light_shader.enableTemperature, + light_shader=light_shader + ) + lgt_mixer_wgt.add_widget(enableTemperature) + + temperature = FloatParam(parent=self, + param="temperature", + label="Temperature", + min=1000.0, + max=50000.0, + value=light_shader.temperature, + light_shader=light_shader + ) + lgt_mixer_wgt.add_widget(temperature) + + color_picker = ColorButton(parent=self, + color=light_shader.lightColor, + param="lightColor", + label="Light Color", + light_shader=light_shader + ) + + lgt_mixer_wgt.add_widget(color_picker) + + self.light_mixer_wgt.add_widget(lgt_mixer_wgt) + + self.mixerGroupObjects.expandAll() + + def mixer_groups_index_changed(self): + idx = int(self.mixerGroups.currentRow()) + current_item = self.mixerGroups.currentItem() + if current_item: + self.label_2.setText("Lights (%s)" % current_item.text()) + else: + return + context = bpy.context + scene = context.scene + rm = scene.renderman + rm.light_mixer_groups_index = idx + + self.refresh_group_objects() + + def mixer_group_objects_selection(self, selected, deselected): + idx = int(self.mixerGroups.currentRow()) + current_item = self.mixerGroups.currentItem() + if not current_item: + self.remove_light_btn.setEnabled(False) + return + self.remove_light_btn.setEnabled(True) class RENDERMAN_UL_LightMixer_Group_Members_List(bpy.types.UIList): @@ -137,6 +774,15 @@ def __init__(self): self.event = None def invoke(self, context, event): + if using_qt() and show_wip_qt(): + global __LIGHT_MIXER_WINDOW__ + if sys.platform == "darwin": + rfb_qt.run_with_timer(__LIGHT_MIXER_WINDOW__, LightMixerQtWrapper) + else: + bpy.ops.wm.light_mixer_qt_app_timed() + + return {'FINISHED'} + wm = context.window_manager width = rfb_config['editor_preferences']['lightmixer_editor']['width'] @@ -211,6 +857,9 @@ def draw_item(self, layout, context, item): RENDERMAN_UL_LightMixer_Group_Members_List ] +if not bpy.app.background: + classes.append(LightMixerQtAppTimed) + def register(): from ...rfb_utils import register_utils diff --git a/rman_operators/rman_operators_editors/rman_operators_editors_tracegroups.py b/rman_operators/rman_operators_editors/rman_operators_editors_tracegroups.py index cf585c15..ff8ecaa3 100644 --- a/rman_operators/rman_operators_editors/rman_operators_editors_tracegroups.py +++ b/rman_operators/rman_operators_editors/rman_operators_editors_tracegroups.py @@ -1,12 +1,353 @@ from bpy.props import (StringProperty, BoolProperty, EnumProperty) from ...rman_ui.rman_ui_base import CollectionPanel -from ...rfb_logger import rfb_log from ...rman_operators.rman_operators_collections import return_empty_list from ...rman_config import __RFB_CONFIG_DICT__ as rfb_config +from ...rfb_utils.prefs_utils import using_qt, show_wip_qt import bpy import re +import sys +__TRACE_GROUPS_WINDOW__ = None + +if not bpy.app.background: + from ...rman_ui import rfb_qt as rfb_qt + from PySide2 import QtCore, QtWidgets, QtGui + + class TraceGroupsQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.trace_groups_qt_app_timed" + bl_label = "RenderMan Trace Sets Editor" + + def __init__(self): + super(TraceGroupsQtAppTimed, self).__init__() + + def execute(self, context): + self._window = TraceGroupsQtWrapper() + return super(TraceGroupsQtAppTimed, self).execute(context) + + class StandardItem(QtGui.QStandardItem): + def __init__(self, txt=''): + super().__init__() + self.setEditable(False) + self.setText(txt) + + class TraceGroupsQtWrapper(rfb_qt.RmanQtWrapper): + def __init__(self) -> None: + super(TraceGroupsQtWrapper, self).__init__() + + self.setWindowTitle('RenderMan Trace Groups') + self.resize(620, 475) + self.buttonBox = QtWidgets.QDialogButtonBox(self) + self.buttonBox.setGeometry(QtCore.QRect(260, 440, 341, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + + # hide OK and cancel buttons + #self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + + self.buttonBox.setObjectName("buttonBox") + self.addButton = QtWidgets.QPushButton(self) + self.addButton.setGeometry(QtCore.QRect(280, 30, 31, 26)) + self.addButton.setObjectName("addButton") + self.addButton.setText("+") + self.removeButton = QtWidgets.QPushButton(self) + self.removeButton.setGeometry(QtCore.QRect(280, 50, 31, 26)) + self.removeButton.setObjectName("removeButton") + self.removeButton.setText("-") + + self.traceGroupObjects = QtWidgets.QTreeView(self) + self.traceGroupObjects.setHeaderHidden(True) + self.treeModel = QtGui.QStandardItemModel(self) + self.rootNode = self.treeModel.invisibleRootItem() + self.traceGroupObjects.setModel(self.treeModel) + + self.traceGroupObjects.setGeometry(QtCore.QRect(30, 250, 441, 192)) + self.traceGroupObjects.setObjectName("traceGroupObjects") + self.traceGroupObjects.setSelectionMode( + QtWidgets.QAbstractItemView.MultiSelection + ) + + self.traceGroups = QtWidgets.QListWidget(self) + self.traceGroups.setGeometry(QtCore.QRect(30, 30, 256, 192)) + self.traceGroups.setObjectName("traceGroups") + + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(40, 10, 91, 17)) + self.label.setText("Trace Groups") + + self.label_2 = QtWidgets.QLabel(self) + self.label_2.setGeometry(QtCore.QRect(40, 230, 200, 17)) + self.label_2.setText("Objects") + + self.refresh_btn = QtWidgets.QPushButton(self) + self.refresh_btn.setGeometry(QtCore.QRect(470, 250, 100, 26)) + self.refresh_btn.setText("Refresh") + self.setToolTip("""Click this if the objects list is out of sync with the scene""" ) + + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) + QtCore.QMetaObject.connectSlotsByName(self) + + self.addButton.clicked.connect(self.add_group) + self.removeButton.clicked.connect(self.remove_group) + self.refresh_btn.clicked.connect(self.refresh_group_objects) + + self.traceGroups.itemChanged.connect(self.trace_group_changed) + self.traceGroups.itemSelectionChanged.connect(self.trace_groups_index_changed) + self.traceGroupObjects.selectionModel().selectionChanged.connect(self.trace_group_objects_selection) + + self.refresh_groups() + self.refresh_group_objects() + self.checkTraceGroups() + + self.traceGroupObjects.expandAll() + + self.add_handlers() + + def closeEvent(self, event): + self.remove_handlers() + super(TraceGroupsQtWrapper, self).closeEvent(event) + + def add_handlers(self): + if self.depsgraph_update_post not in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.append(self.depsgraph_update_post) + + def remove_handlers(self): + if self.depsgraph_update_post in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(self.depsgraph_update_post) + + def depsgraph_update_post(self, bl_scene, depsgraph): + for dps_update in reversed(depsgraph.updates): + if isinstance(dps_update.id, bpy.types.Collection): + #self.refresh_groups() + self.traceGroups.setCurrentRow(-1) + self.refresh_group_objects() + #elif isinstance(dps_update.id, bpy.types.Scene): + # self.trace_groups_index_changed() + + def checkTraceGroups(self): + if self.traceGroups.count() < 1: + self.traceGroupObjects.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.enable_trace_group_objects(self.rootNode, enable=False) + self.removeButton.setEnabled(False) + else: + self.traceGroupObjects.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.removeButton.setEnabled(True) + self.enable_trace_group_objects(self.rootNode, enable=True) + + def update(self): + idx = int(self.traceGroups.currentRow()) + self.addButton.setEnabled(True) + + self.checkTraceGroups() + super(TraceGroupsQtWrapper, self).update() + + def refresh_groups(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + self.traceGroups.clear() + for grp in rm.object_groups: + item = QtWidgets.QListWidgetItem(grp.name) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + self.traceGroups.addItem(item) + + if self.traceGroups.count() > 0: + self.traceGroups.setCurrentRow(rm.object_groups_index) + + def add_group(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + grp = rm.object_groups.add() + grp.name = 'traceGroup_%d' % len(rm.object_groups) + rm.object_groups_index = len(rm.object_groups)-1 + self.refresh_groups() + + def remove_group(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + index = rm.object_groups_index + group = rm.object_groups[index] + # get a list of all objects in this group + ob_list = [member.ob_pointer for member in group.members] + rm.object_groups.remove(index) + rm.object_groups_index -= 1 + + # now tell each object to update + for ob in ob_list: + ob.update_tag(refresh={'OBJECT'}) + + self.refresh_groups() + + def trace_group_changed(self, item): + idx = int(self.traceGroups.currentRow()) + + context = bpy.context + scene = context.scene + rm = scene.renderman + grp = rm.object_groups[idx] + grp.name = item.text() + self.label_2.setText("Objects (%s)" % item.text()) + for member in grp.members: + ob = member.ob_pointer + ob.update_tag(refresh={'OBJECT'}) + + def find_item(self, standard_item, ob): + ''' + if standard_item.text() == ob.name: + return standard_item + + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + if item.text() == ob.name: + return item + if item.hasChildren(): + return self.find_item(item, ob) + ''' + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + if item.text() == ob.name: + return item + + return None + + def enable_trace_group_objects(self, standard_item, enable=True): + standard_item.setEnabled(enable) + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + item.setEnabled(enable) + if item.hasChildren(): + return self.enable_trace_group_objects(item, enable=enable) + + def refresh_group_objects(self): + idx = int(self.traceGroups.currentRow()) + enabled = True + if idx == -1: + enabled = False + self.label_2.setText("Objects (no group selected)") + context = bpy.context + scene = context.scene + rm = scene.renderman + + self.treeModel.clear() + self.rootNode = self.treeModel.invisibleRootItem() + + def add_children(root_item, ob): + for child in ob.children: + if child.type in ['CAMERA', 'ARMATURE']: + continue + item = self.find_item(root_item, child) + if not item: + item = StandardItem(txt=child.name) + root_item.appendRow(item) + if len(child.children) > 0: + add_children(item, child) + + root_parents = [ob for ob in scene.objects if ob.parent is None] + for ob in root_parents: + if ob.type in ('ARMATURE', 'CAMERA'): + continue + + item = self.find_item(self.rootNode, ob) + if not item: + item = StandardItem(txt=ob.name) + self.rootNode.appendRow(item) + if len(ob.children) > 0: + add_children(item, ob) + + self.traceGroupObjects.expandAll() + if idx != -1: + self.trace_groups_index_changed() + + def bl_select_objects(self, obs): + context = bpy.context + for ob in context.selected_objects: + ob.select_set(False) + for ob in obs: + ob.select_set(True) + context.view_layer.objects.active = ob + + def trace_groups_index_changed(self): + idx = int(self.traceGroups.currentRow()) + current_item = self.traceGroups.currentItem() + self.checkTraceGroups() + if current_item: + self.label_2.setText("Objects (%s)" % current_item.text()) + else: + return + context = bpy.context + scene = context.scene + rm = scene.renderman + rm.object_groups_index = idx + + group_index = rm.object_groups_index + object_groups = rm.object_groups + object_group = object_groups[group_index] + + selected_items = QtCore.QItemSelection() + obs = [] + for member in object_group.members: + ob = member.ob_pointer + if ob is None: + continue + item = self.find_item(self.rootNode, ob) + if item: + idx = self.treeModel.indexFromItem(item) + selection_range = QtCore.QItemSelectionRange(idx) + selected_items.append(selection_range) + obs.append(ob) + self.traceGroupObjects.selectionModel().select(selected_items, QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.NoUpdate) + self.bl_select_objects(obs) + + def trace_group_objects_selection(self, selected, deselected): + idx = int(self.traceGroups.currentRow()) + current_item = self.traceGroups.currentItem() + if not current_item: + return + + context = bpy.context + scene = context.scene + rm = scene.renderman + + group_index = rm.object_groups_index + object_groups = rm.object_groups + if group_index not in range(0, len(object_groups)): + return + object_group = object_groups[group_index] + + for i in deselected.indexes(): + item = self.traceGroupObjects.model().itemFromIndex(i) + ob = bpy.data.objects.get(item.text(), None) + if ob is None: + continue + for i, member in enumerate(object_group.members): + if ob == member.ob_pointer: + object_group.members.remove(i) + ob.update_tag(refresh={'OBJECT'}) + break + + obs = [] + for i in selected.indexes(): + item = self.traceGroupObjects.model().itemFromIndex(i) + ob = bpy.data.objects.get(item.text(), None) + if ob is None: + continue + do_add = True + for member in object_group.members: + if ob == member.ob_pointer: + do_add = False + obs.append(member.ob_pointer) + if do_add: + obs.append(ob) + ob_in_group = object_group.members.add() + ob_in_group.name = ob.name + ob_in_group.ob_pointer = ob + ob.update_tag(refresh={'OBJECT'}) + self.bl_select_objects(obs) + class RENDERMAN_UL_Object_Group_List(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): @@ -160,6 +501,15 @@ def check_tracegroups(self, context): def invoke(self, context, event): + if using_qt() and show_wip_qt(): + global __TRACE_GROUPS_WINDOW__ + if sys.platform == "darwin": + rfb_qt.run_with_timer(__TRACE_GROUPS_WINDOW__, TraceGroupsQtWrapper) + else: + bpy.ops.wm.trace_groups_qt_app_timed() + + return {'FINISHED'} + wm = context.window_manager width = rfb_config['editor_preferences']['tracesets_editor']['width'] self.event = event @@ -168,9 +518,12 @@ def invoke(self, context, event): classes = [ PRMAN_OT_Renderman_Open_Groups_Editor, - RENDERMAN_UL_Object_Group_List + RENDERMAN_UL_Object_Group_List, ] +if not bpy.app.background: + classes.append(TraceGroupsQtAppTimed) + def register(): from ...rfb_utils import register_utils diff --git a/rman_operators/rman_operators_editors/rman_operators_editors_vol_aggregates.py b/rman_operators/rman_operators_editors/rman_operators_editors_vol_aggregates.py index d9d8489e..f5d895fb 100644 --- a/rman_operators/rman_operators_editors/rman_operators_editors_vol_aggregates.py +++ b/rman_operators/rman_operators_editors/rman_operators_editors_vol_aggregates.py @@ -1,22 +1,347 @@ from bpy.props import (StringProperty, BoolProperty, EnumProperty) from ...rman_ui.rman_ui_base import CollectionPanel -from ...rfb_logger import rfb_log from ...rman_operators.rman_operators_collections import return_empty_list from ...rman_config import __RFB_CONFIG_DICT__ as rfb_config from ...rfb_utils import scene_utils +from ... import rfb_icons +from ...rfb_utils.prefs_utils import using_qt, show_wip_qt import bpy import re +import sys + +__VOL_AGGREGATE_WINDOW__ = None + +if not bpy.app.background: + from ...rman_ui import rfb_qt as rfb_qt + from PySide2 import QtCore, QtWidgets, QtGui + + class VolAggregateQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.vol_aggregates_qt_app_timed" + bl_label = "RenderMan Volume Aggregates Editor" + + def __init__(self): + super(VolAggregateQtAppTimed, self).__init__() + + def execute(self, context): + self._window = VolAggregatesQtWrapper() + return super(VolAggregateQtAppTimed, self).execute(context) + + class StandardItem(QtGui.QStandardItem): + def __init__(self, txt=''): + super().__init__() + self.setEditable(False) + self.setText(txt) + + class VolAggregatesQtWrapper(rfb_qt.RmanQtWrapper): + def __init__(self) -> None: + super(VolAggregatesQtWrapper, self).__init__() + + self.setWindowTitle('RenderMan Volume Aggregates') + self.resize(620, 475) + self.buttonBox = QtWidgets.QDialogButtonBox(self) + self.buttonBox.setGeometry(QtCore.QRect(260, 440, 341, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + + # hide OK and cancel buttons + #self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + + self.buttonBox.setObjectName("buttonBox") + self.addButton = QtWidgets.QPushButton(self) + self.addButton.setGeometry(QtCore.QRect(280, 30, 31, 26)) + self.addButton.setObjectName("addButton") + self.addButton.setText("+") + self.removeButton = QtWidgets.QPushButton(self) + self.removeButton.setGeometry(QtCore.QRect(280, 50, 31, 26)) + self.removeButton.setObjectName("removeButton") + self.removeButton.setText("-") + + self.volAggregateGroupObjects = QtWidgets.QTreeView(self) + self.volAggregateGroupObjects.setHeaderHidden(True) + self.treeModel = QtGui.QStandardItemModel(self) + self.rootNode = self.treeModel.invisibleRootItem() + self.volAggregateGroupObjects.setModel(self.treeModel) + + self.volAggregateGroupObjects.setGeometry(QtCore.QRect(30, 250, 441, 192)) + self.volAggregateGroupObjects.setObjectName("volAggregateGroupObjects") + self.volAggregateGroupObjects.setSelectionMode( + QtWidgets.QAbstractItemView.MultiSelection + ) + + self.volAggregateGroups = QtWidgets.QListWidget(self) + self.volAggregateGroups.setGeometry(QtCore.QRect(30, 30, 256, 192)) + self.volAggregateGroups.setObjectName("volAggregateGroups") + + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(40, 10, 91, 17)) + self.label.setText("Volume Aggregates") + + self.label_2 = QtWidgets.QLabel(self) + self.label_2.setGeometry(QtCore.QRect(40, 230, 200, 17)) + self.label_2.setText("Objects") + + self.refresh_btn = QtWidgets.QPushButton(self) + self.refresh_btn.setGeometry(QtCore.QRect(470, 250, 100, 26)) + self.refresh_btn.setText("Refresh") + self.setToolTip("""Click this if the objects list is out of sync with the scene""" ) + + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) + QtCore.QMetaObject.connectSlotsByName(self) + + self.addButton.clicked.connect(self.add_group) + self.removeButton.clicked.connect(self.remove_group) + self.refresh_btn.clicked.connect(self.refresh_group_objects) + + self.volAggregateGroups.itemChanged.connect(self.vol_aggregate_group_changed) + self.volAggregateGroups.itemSelectionChanged.connect(self.vol_aggregate_groups_index_changed) + self.volAggregateGroupObjects.selectionModel().selectionChanged.connect(self.vol_aggregate_group_objects_selection) + + self.refresh_groups() + self.refresh_group_objects() + self.checkvolAggregateGroups() + + self.volAggregateGroupObjects.expandAll() + + self.add_handlers() + + def closeEvent(self, event): + self.remove_handlers() + super(VolAggregatesQtWrapper, self).closeEvent(event) + + def add_handlers(self): + if self.depsgraph_update_post not in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.append(self.depsgraph_update_post) + + def remove_handlers(self): + if self.depsgraph_update_post in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(self.depsgraph_update_post) + + def depsgraph_update_post(self, bl_scene, depsgraph): + for dps_update in reversed(depsgraph.updates): + if isinstance(dps_update.id, bpy.types.Collection): + self.volAggregateGroups.setCurrentRow(-1) + self.refresh_group_objects() + + def checkvolAggregateGroups(self): + if self.volAggregateGroups.count() < 1: + self.volAggregateGroupObjects.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.vol_aggregate_group_objects(self.rootNode, enable=False) + self.removeButton.setEnabled(False) + else: + self.volAggregateGroupObjects.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.removeButton.setEnabled(True) + self.vol_aggregate_group_objects(self.rootNode, enable=True) + + def update(self): + idx = int(self.volAggregateGroups.currentRow()) + self.addButton.setEnabled(True) + + self.checkvolAggregateGroups() + super(VolAggregatesQtWrapper, self).update() + + def refresh_groups(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + self.volAggregateGroups.clear() + for i, grp in enumerate(rm.vol_aggregates): + if i == 0: + continue + item = QtWidgets.QListWidgetItem(grp.name) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + self.volAggregateGroups.addItem(item) + + if self.volAggregateGroups.count() > 0: + self.volAggregateGroups.setCurrentRow(rm.vol_aggregates_index) + + def add_group(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + grp = rm.vol_aggregates.add() + grp.name = 'VolumeAggreagte_%d' % (len(rm.vol_aggregates)-2) + rm.vol_aggregates_index = len(rm.vol_aggregates)-1 + self.refresh_groups() + + def remove_group(self): + context = bpy.context + scene = context.scene + rm = scene.renderman + + index = rm.vol_aggregates_index + group = rm.vol_aggregates[index] + # get a list of all objects in this group + ob_list = [member.ob_pointer for member in group.members] + rm.vol_aggregates.remove(index) + rm.vol_aggregates_index -= 1 + + # now tell each object to update + for ob in ob_list: + ob.update_tag(refresh={'DATA'}) + + self.refresh_groups() + + def vol_aggregate_group_changed(self, item): + idx = int(self.volAggregateGroups.currentRow()) + + context = bpy.context + scene = context.scene + rm = scene.renderman + grp = rm.vol_aggregates[idx+1] + grp.name = item.text() + self.label_2.setText("Objects (%s)" % item.text()) + for member in grp.members: + ob = member.ob_pointer + ob.update_tag(refresh={'DATA'}) + + def find_item(self, standard_item, ob): + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + if item.text() == ob.name: + return item + + return None + + def vol_aggregate_group_objects(self, standard_item, enable=True): + standard_item.setEnabled(enable) + for i in range(0, standard_item.rowCount()): + item = standard_item.child(i) + item.setEnabled(enable) + if item.hasChildren(): + return self.vol_aggregate_group_objects(item, enable=enable) + + def refresh_group_objects(self): + idx = int(self.volAggregateGroups.currentRow()) + enabled = True + if idx == -1: + enabled = False + self.label_2.setText("Objects (no group selected)") + context = bpy.context + scene = context.scene + rm = scene.renderman + + self.treeModel.clear() + self.rootNode = self.treeModel.invisibleRootItem() + + root_parents = scene_utils.get_all_volume_objects(scene) + for ob in root_parents: + + item = self.find_item(self.rootNode, ob) + if not item: + item = StandardItem(txt=ob.name) + self.rootNode.appendRow(item) + + self.volAggregateGroupObjects.expandAll() + if idx != -1: + self.vol_aggregate_groups_index_changed() + + def bl_select_objects(self, obs): + context = bpy.context + for ob in context.selected_objects: + ob.select_set(False) + for ob in obs: + ob.select_set(True) + context.view_layer.objects.active = ob + + def vol_aggregate_groups_index_changed(self): + idx = int(self.volAggregateGroups.currentRow()) + current_item = self.volAggregateGroups.currentItem() + self.checkvolAggregateGroups() + if current_item: + self.label_2.setText("Objects (%s)" % current_item.text()) + else: + return + context = bpy.context + scene = context.scene + rm = scene.renderman + rm.vol_aggregates_index = idx + 1 + + group_index = rm.vol_aggregates_index + vol_aggregates = rm.vol_aggregates + object_group = vol_aggregates[group_index] + + selected_items = QtCore.QItemSelection() + obs = [] + for member in object_group.members: + ob = member.ob_pointer + if ob is None: + continue + item = self.find_item(self.rootNode, ob) + if item: + idx = self.treeModel.indexFromItem(item) + selection_range = QtCore.QItemSelectionRange(idx) + selected_items.append(selection_range) + obs.append(ob) + self.volAggregateGroupObjects.selectionModel().select(selected_items, QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.NoUpdate) + self.bl_select_objects(obs) + + def vol_aggregate_group_objects_selection(self, selected, deselected): + idx = int(self.volAggregateGroups.currentRow()) + current_item = self.volAggregateGroups.currentItem() + if not current_item: + return + + context = bpy.context + scene = context.scene + rm = scene.renderman + + group_index = rm.vol_aggregates_index + vol_aggregates = rm.vol_aggregates + if group_index not in range(0, len(vol_aggregates)): + return + object_group = vol_aggregates[group_index] + + for i in deselected.indexes(): + item = self.volAggregateGroupObjects.model().itemFromIndex(i) + ob = bpy.data.objects.get(item.text(), None) + if ob is None: + continue + for i, member in enumerate(object_group.members): + if ob == member.ob_pointer: + object_group.members.remove(i) + ob.update_tag(refresh={'DATA'}) + break + + obs = [] + for i in selected.indexes(): + item = self.volAggregateGroupObjects.model().itemFromIndex(i) + ob = bpy.data.objects.get(item.text(), None) + if ob is None: + continue + do_add = True + for member in object_group.members: + if ob == member.ob_pointer: + do_add = False + obs.append(member.ob_pointer) + if do_add: + obs.append(ob) + ob_in_group = object_group.members.add() + ob_in_group.name = ob.name + ob_in_group.ob_pointer = ob + ob.update_tag(refresh={'DATA'}) + self.bl_select_objects(obs) class RENDERMAN_UL_Volume_Aggregates_List(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + rman_vol_agg = rfb_icons.get_icon("rman_vol_aggregates").icon_id + if index == 0: + layout.label(text=item.name, icon_value=rman_vol_agg) + else: + layout.prop(item, 'name', text='', emboss=False, icon_value=rman_vol_agg) + + +class RENDERMAN_UL_Volume_Aggregates_Objects_List(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - custom_icon = 'OBJECT_DATAMODE' + custom_icon = 'OUTLINER_OB_VOLUME' layout.context_pointer_set("selected_obj", item.ob_pointer) op = layout.operator('renderman.remove_from_vol_aggregate', text='', icon='REMOVE') label = item.ob_pointer.name - layout.label(text=label, icon=custom_icon) + layout.label(text=label, icon=custom_icon) class PRMAN_OT_Renderman_Open_Volume_Aggregates_Editor(CollectionPanel, bpy.types.Operator): @@ -87,7 +412,14 @@ def draw(self, context): "renderman.add_remove_volume_aggregates", "scene.renderman", "vol_aggregates", "vol_aggregates_index", - default_name='VolumeAggreagte_%d' % len(rm.vol_aggregates)) + default_name='VolumeAggreagte_%d' % (len(rm.vol_aggregates)-2), + ui_list_class="RENDERMAN_UL_Volume_Aggregates_List", + enable_remove_func=self.enable_remove_func) + + def enable_remove_func(self, context): + scene = context.scene + rm = scene.renderman + return (rm.vol_aggregates_index != 0) def draw_objects_item(self, layout, context, item): row = layout.row() @@ -95,6 +427,35 @@ def draw_objects_item(self, layout, context, item): rm = scene.renderman vol_aggregate = rm.vol_aggregates[rm.vol_aggregates_index] + if rm.vol_aggregates_index == 0: + # we're viewing the global volume aggregate + # just display what volumes are in the global aggregate + # and don't allow the user to edit the list + box = layout.box() + box.use_property_split = True + box.use_property_decorate = False + + # Loop over all of volume objects in the scene. + # Check if they already belong to aggregate. If they do, they + # are not the global aggregate. + for ob in scene_utils.get_all_volume_objects(scene): + if not ob.renderman.volume_global_aggregate: + # volume is should not be in the global aggregate + continue + do_draw = True + for lg in rm.vol_aggregates: + for member in lg.members: + if member.ob_pointer == ob: + do_draw = False + break + if not do_draw: + break + if do_draw: + row = box.row(align=True) + custom_icon = 'OUTLINER_OB_VOLUME' + row.label(text=ob.name, icon=custom_icon) + return + row = layout.row() row.separator() @@ -132,7 +493,7 @@ def draw_objects_item(self, layout, context, item): row = layout.row() - row.template_list('RENDERMAN_UL_Volume_Aggregates_List', "", + row.template_list('RENDERMAN_UL_Volume_Aggregates_Objects_List', "", vol_aggregate, "members", vol_aggregate, 'members_index', rows=6) def draw_item(self, layout, context, item): @@ -162,6 +523,14 @@ def check_aggregates(self, context): lg.members_index -= 1 def invoke(self, context, event): + if using_qt() and show_wip_qt(): + global __VOL_AGGREGATE_WINDOW__ + if sys.platform == "darwin": + rfb_qt.run_with_timer(__VOL_AGGREGATE_WINDOW__, VolAggregatesQtWrapper) + else: + bpy.ops.wm.vol_aggregates_qt_app_timed() + + return {'FINISHED'} wm = context.window_manager width = rfb_config['editor_preferences']['vol_aggregates_editor']['width'] @@ -171,9 +540,13 @@ def invoke(self, context, event): classes = [ PRMAN_OT_Renderman_Open_Volume_Aggregates_Editor, - RENDERMAN_UL_Volume_Aggregates_List + RENDERMAN_UL_Volume_Aggregates_List, + RENDERMAN_UL_Volume_Aggregates_Objects_List, ] +if not bpy.app.background: + classes.append(VolAggregateQtAppTimed) + def register(): from ...rfb_utils import register_utils diff --git a/rman_operators/rman_operators_nodetree.py b/rman_operators/rman_operators_nodetree.py index 83fa3359..0911f79f 100644 --- a/rman_operators/rman_operators_nodetree.py +++ b/rman_operators/rman_operators_nodetree.py @@ -56,7 +56,7 @@ def execute(self, context): if not ob.visible_diffuse or not ob.visible_glossy: ob.renderman.rman_visibilityIndirect = "0" if not ob.visible_transmission: - ob.renderman.rman_visibilityTransmission = "0" + ob.renderman.rman_visibilityTransmission = "0" if ob.type == 'LIGHT' and not ob.data.use_nodes: if ob.data.type == 'POINT': @@ -96,6 +96,7 @@ def execute(self, context): light.type = 'POINT' light.renderman.use_renderman_node = True + shadergraph_utils.hide_cycles_nodes(light) output = nt.nodes.new('RendermanOutputNode') node_name = rman_bl_nodes.__BL_NODES_MAP__[light_shader] @@ -213,6 +214,7 @@ def execute(self, context): nt = idblock.node_tree if idtype == 'material': + shadergraph_utils.hide_cycles_nodes(idblock) output = nt.nodes.new('RendermanOutputNode') if idblock.grease_pencil: shadergraph_utils.convert_grease_pencil_mat(idblock, nt, output) @@ -263,6 +265,7 @@ def execute(self, context): light.type = 'POINT' light.renderman.use_renderman_node = True + shadergraph_utils.hide_cycles_nodes(light) output = nt.nodes.new('RendermanOutputNode') default = nt.nodes.new('%sLightNode' % @@ -283,6 +286,7 @@ def execute(self, context): elif idtype == 'world': # world + shadergraph_utils.hide_cycles_nodes(idblock) idblock.renderman.use_renderman_node = True if shadergraph_utils.find_node(idblock, 'RendermanIntegratorsOutputNode'): return {'FINISHED'} @@ -362,6 +366,7 @@ def execute(self, context): world.renderman.use_renderman_node = True if shadergraph_utils.find_node(world, 'RendermanIntegratorsOutputNode'): return {'FINISHED'} + shadergraph_utils.hide_cycles_nodes(world) output = nt.nodes.new('RendermanIntegratorsOutputNode') node_name = rman_bl_nodes.__BL_NODES_MAP__.get('PxrPathTracer') default = nt.nodes.new(node_name) @@ -395,7 +400,7 @@ def execute(self, context): world.renderman.use_renderman_node = True if shadergraph_utils.find_node(world, 'RendermanDisplayfiltersOutputNode'): return {'FINISHED'} - + shadergraph_utils.hide_cycles_nodes(world) df_output = nt.nodes.new('RendermanDisplayfiltersOutputNode') df_output.location = df_output.location df_output.location[0] -= 300 @@ -430,7 +435,7 @@ def execute(self, context): world = context.scene.world world.use_nodes = True nt = world.node_tree - + shadergraph_utils.hide_cycles_nodes(world) output = nt.nodes.new('RendermanIntegratorsOutputNode') node_name = rman_bl_nodes.__BL_NODES_MAP__.get('PxrPathTracer') default = nt.nodes.new(node_name) @@ -476,7 +481,7 @@ def execute(self, context): world.renderman.use_renderman_node = True if shadergraph_utils.find_node(world, 'RendermanSamplefiltersOutputNode'): return {'FINISHED'} - + shadergraph_utils.hide_cycles_nodes(world) sf_output = nt.nodes.new('RendermanSamplefiltersOutputNode') sf_output.location = sf_output.location sf_output.location[0] -= 300 @@ -511,7 +516,7 @@ def execute(self, context): ob.active_material = mat mat.use_nodes = True nt = mat.node_tree - + shadergraph_utils.hide_cycles_nodes(mat) output = nt.nodes.new('RendermanOutputNode') bxdf_node_name = rman_bl_nodes.__BL_NODES_MAP__[bxdf_name] default = nt.nodes.new(bxdf_node_name) @@ -572,6 +577,7 @@ def execute(self, context): ob.renderman.rman_material_override = mat mat.use_nodes = True nt = mat.node_tree + shadergraph_utils.hide_cycles_nodes(mat) output = nt.nodes.new('RendermanOutputNode') output.select = False diff --git a/rman_operators/rman_operators_render.py b/rman_operators/rman_operators_render.py index 85d25c7a..160ff198 100644 --- a/rman_operators/rman_operators_render.py +++ b/rman_operators/rman_operators_render.py @@ -94,10 +94,10 @@ def invoke(self, context, event=None): context.window_manager.fileselect_add(self) return{'RUNNING_MODAL'} -class PRMAN_OT_ExternalRendermanBake(bpy.types.Operator): - bl_idname = "renderman.external_bake" - bl_label = "External Baking" - bl_description = "Spool an external bake render." +class PRMAN_OT_BatchRendermanBake(bpy.types.Operator): + bl_idname = "renderman.batch_bake_render" + bl_label = "Batch Baking" + bl_description = "Spool a batch bake render." bl_options = {'INTERNAL'} def execute(self, context): @@ -118,15 +118,15 @@ def execute(self, context): return {'FINISHED'} -class PRMAN_OT_ExternalRender(bpy.types.Operator): +class PRMAN_OT_BatchRender(bpy.types.Operator): '''''' - bl_idname = "renderman.external_render" - bl_label = "External Render" - bl_description = "Launch a spooled external render." + bl_idname = "renderman.batch_render" + bl_label = "Batch Render" + bl_description = "Launch a spooled batch render." bl_options = {'INTERNAL'} - def external_blender_batch(self, context): + def blender_batch_render(self, context): rm = context.scene.renderman if rm.queuing_system != 'none': from .. import rman_spool @@ -162,7 +162,7 @@ def external_blender_batch(self, context): else: self.report({'ERROR'}, 'Queuing system set to none') - def external_rib_render(self, context): + def rib_batch_render(self, context): scene = context.scene rm = scene.renderman if not rm.is_rman_interactive_running: @@ -180,9 +180,9 @@ def execute(self, context): rm = scene.renderman if not rm.is_rman_interactive_running: if scene.renderman.spool_style == 'rib': - self.external_rib_render(context) + self.rib_batch_render(context) else: - self.external_blender_batch(context) + self.blender_batch_render(context) else: self.report({"ERROR"}, "Viewport rendering is on.") return {'FINISHED'} @@ -192,13 +192,30 @@ class PRMAN_OT_StartInteractive(bpy.types.Operator): '''''' bl_idname = "renderman.start_ipr" bl_label = "Start Interactive Rendering" - bl_description = "Start Interactive Rendering" + bl_description = "Start IPR and render to the viewport" bl_options = {'INTERNAL'} + render_to_it: bpy.props.BoolProperty(default=False) + + @classmethod + def description(cls, context, properties): + if properties.render_to_it: + return "Start IPR and render to 'it'" + return cls.bl_description + def invoke(self, context, event=None): - view = context.space_data - if view and view.shading.type != 'RENDERED': - view.shading.type = 'RENDERED' + scene = context.scene + if self.render_to_it: + rr = RmanRender.get_rman_render() + rr.rman_scene.ipr_render_into = 'it' + depsgraph = context.evaluated_depsgraph_get() + rr.start_interactive_render(context, depsgraph) + else: + view = context.space_data + if view and view.shading.type != 'RENDERED': + rr = RmanRender.get_rman_render() + rr.rman_scene.ipr_render_into = 'blender' + view.shading.type = 'RENDERED' return {'FINISHED'} @@ -211,7 +228,11 @@ class PRMAN_OT_StopInteractive(bpy.types.Operator): bl_options = {'INTERNAL'} def invoke(self, context, event=None): - if context.space_data.type == 'VIEW_3D': + scene = context.scene + rr = RmanRender.get_rman_render() + if rr.is_ipr_to_it(): + rr.stop_render(stop_draw_thread=False) + elif context.space_data.type == 'VIEW_3D': context.space_data.shading.type = 'SOLID' else: for window in bpy.context.window_manager.windows: @@ -303,8 +324,8 @@ def invoke(self, context, event=None): PRMAN_OT_Renderman_Use_Renderman, PRMAN_OT_RendermanBake, PRMAN_OT_RendermanBakeSelectedBrickmap, - PRMAN_OT_ExternalRendermanBake, - PRMAN_OT_ExternalRender, + PRMAN_OT_BatchRendermanBake, + PRMAN_OT_BatchRender, PRMAN_OT_StartInteractive, PRMAN_OT_StopInteractive, PRMAN_OT_StopRender, diff --git a/rman_operators/rman_operators_rib.py b/rman_operators/rman_operators_rib.py index 03790763..a004d2fd 100644 --- a/rman_operators/rman_operators_rib.py +++ b/rman_operators/rman_operators_rib.py @@ -32,7 +32,6 @@ def invoke(self, context, event=None): rm.rib_format = format_prev_val rib_output = string_utils.expand_string(rm.path_rib_output, - frame=scene.frame_current, asFilePath=True) filepath_utils.view_file(rib_output) diff --git a/rman_operators/rman_operators_stylized.py b/rman_operators/rman_operators_stylized.py index f5dd941c..4e832d85 100644 --- a/rman_operators/rman_operators_stylized.py +++ b/rman_operators/rman_operators_stylized.py @@ -86,11 +86,11 @@ def add_manifolds(self, nt, pattern_node): def attach_pattern(self, context, ob): mat = object_utils.get_active_material(ob) - if not mat: + if mat is None: bpy.ops.object.rman_add_bxdf('EXEC_DEFAULT', bxdf_name='PxrSurface') mat = object_utils.get_active_material(ob) - if not mat: + if mat is None: self.report({'ERROR'}, 'Cannot find a material for: %s' % ob.name) nt = mat.node_tree @@ -144,12 +144,12 @@ def attach_pattern(self, context, ob): val = param_settings['value'] setattr(pattern_node, param_name, val) - array_len = getattr(node, coll_idx_nm) - sub_prop_nm = '%s[%d]' % (prop_name, array_len-1) + idx = getattr(node, coll_idx_nm) + sub_prop_nm = '%s[%d]' % (prop_name, idx) nt.links.new(pattern_node.outputs['resultAOV'], node.inputs[sub_prop_nm]) # Add manifolds - self.add_manifolds(nt, pattern_node) + self.add_manifolds(nt, pattern_node) else: if node.inputs[prop_name].is_linked: diff --git a/rman_operators/rman_operators_utils.py b/rman_operators/rman_operators_utils.py index fc19b4cf..5b1aac20 100644 --- a/rman_operators/rman_operators_utils.py +++ b/rman_operators/rman_operators_utils.py @@ -4,6 +4,8 @@ from ..rfb_utils import texture_utils from ..rfb_utils import filepath_utils from ..rfb_utils import object_utils +from ..rfb_utils import upgrade_utils +from .. import rman_constants from bpy.types import Operator from bpy.props import StringProperty, FloatProperty import os @@ -11,6 +13,33 @@ import bpy import shutil +class PRMAN_OT_Renderman_Upgrade_Scene(Operator): + """An operator to upgrade the scene to the current version of RenderMan.""" + + bl_idname = "renderman.upgrade_scene" + bl_label = "Upgrade Scene" + bl_description = "Upgrade your scene to the current version" + bl_options = {'INTERNAL'} + + @classmethod + def poll(cls, context): + if context.engine != "PRMAN_RENDER": + return False + + scene = context.scene + version = scene.renderman.renderman_version + if version == '': + return True + + if version < rman_constants.RMAN_SUPPORTED_VERSION_STRING: + return True + + return False + + def execute(self, context): + upgrade_utils.upgrade_scene(None) + return {'FINISHED'} + class PRMAN_OT_Renderman_Package(Operator): """An operator to create a zip archive of the current scene.""" @@ -313,27 +342,10 @@ def invoke(self, context, event): return {'RUNNING_MODAL'} -class PRMAN_OT_Renderman_Open_Addon_Preferences(bpy.types.Operator): - bl_idname = "renderman.open_addon_preferences" - bl_label = "Addon Preferences" - bl_description = "Open the RenderMan for Blender addon prferences" - bl_options = {'INTERNAL'} - - - @classmethod - def poll(cls, context): - return context.engine == "PRMAN_RENDER" - - def execute(self, context): - context.preferences.active_section = 'ADDONS' - context.window_manager.addon_search = 'RenderMan For Blender' - bpy.ops.screen.userpref_show() - return {"FINISHED"} - classes = [ + PRMAN_OT_Renderman_Upgrade_Scene, PRMAN_OT_Renderman_Package, - PRMAN_OT_Renderman_Start_Debug_Server, - PRMAN_OT_Renderman_Open_Addon_Preferences + PRMAN_OT_Renderman_Start_Debug_Server ] def register(): diff --git a/rman_operators/rman_operators_view3d.py b/rman_operators/rman_operators_view3d.py index 8d7f61ad..fd4ace8f 100644 --- a/rman_operators/rman_operators_view3d.py +++ b/rman_operators/rman_operators_view3d.py @@ -6,6 +6,7 @@ from ..rfb_utils import shadergraph_utils from ..rfb_utils import object_utils from ..rfb_utils import string_utils +from ..rfb_utils import prefs_utils from ..rfb_logger import rfb_log from .. import rfb_icons from ..rman_constants import RFB_ADDON_VERSION_STRING @@ -78,15 +79,13 @@ def description(cls, context, properties): def execute(self, context): - if self.properties.bl_prim_type != '': - bpy.ops.object.add(type=self.properties.bl_prim_type) - else: - bpy.ops.object.add(type='EMPTY') - ob = None - for o in context.selected_objects: - ob = o - break + nm = self.rman_default_name + data_block = None + if self.properties.bl_prim_type == 'VOLUME': + data_block = bpy.data.volumes.new(nm) + + ob = bpy.data.objects.new(nm, data_block) ob.empty_display_type = 'PLAIN_AXES' rm = ob.renderman @@ -98,23 +97,33 @@ def execute(self, context): ob.name = 'Ri%s' % rm.rman_quadric_type.capitalize() if rm.rman_quadric_type == 'SPHERE': ob.empty_display_type = 'SPHERE' - else: - if self.properties.rman_default_name != '': - ob.name = self.properties.rman_default_name + else: if rm.primitive == 'RI_VOLUME': ob.empty_display_type = 'CUBE' - bpy.ops.node.rman_new_material_override('EXEC_DEFAULT', bxdf_name='PxrVolume') + mat = shadergraph_utils.create_bxdf('PxrVolume') + ob.renderman.rman_material_override = mat elif self.properties.bl_prim_type == 'VOLUME': - bpy.ops.object.rman_add_bxdf('EXEC_DEFAULT', bxdf_name='PxrVolume') - mat = ob.active_material - nt = mat.node_tree + mat = shadergraph_utils.create_bxdf('PxrVolume') + ob.active_material = mat output = shadergraph_utils.find_node(mat, 'RendermanOutputNode') - bxdf = output.inputs['Bxdf'].links[0].from_node + bxdf = output.inputs['bxdf_in'].links[0].from_node bxdf.densityFloatPrimVar = 'density' - if self.properties.rman_open_filebrowser: + if self.properties.rman_open_filebrowser or self.filepath != "": if rm.primitive == 'DELAYED_LOAD_ARCHIVE': + ob.empty_display_type = 'CUBE' rm.path_archive = self.properties.filepath + # try to get the bounding box from the RIB file + with open(rm.path_archive) as f: + for ln in f.readlines(): + if not ln.startswith('##bbox: '): + continue + tokens = ln.replace('##bbox: ', '').split(' ') + min_x, max_x, min_y, max_y, min_z, max_z = tokens + max_scale = max(max(float(max_x), float(max_y)), float(max_z)) + ob.empty_display_size = max_scale + break + elif rm.primitive == 'PROCEDURAL_RUN_PROGRAM': rm.runprogram_path = self.properties.filepath elif rm.primitive == 'DYNAMIC_LOAD_DSO': @@ -130,7 +139,11 @@ def execute(self, context): yup_to_zup = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X') ob.matrix_world = yup_to_zup @ ob.matrix_world - ob.update_tag(refresh={'DATA'}) + context.scene.collection.objects.link(ob) + if context.view_layer.objects.active: + context.view_layer.objects.active.select_set(False) + ob.select_set(True) + context.view_layer.objects.active = ob return {"FINISHED"} @@ -183,23 +196,16 @@ def description(cls, context, properties): return info def execute(self, context): - bpy.ops.object.light_add(type='AREA') - light_ob = getattr(context, 'object', None) - if not light_ob: - if hasattr(context, 'selected_objects'): - light_ob = context.selected_objects[0] - else: - scene = context.scene - light_ob = scene.view_layers[0].objects.active - - light_ob.data.renderman.renderman_light_role = 'RMAN_LIGHT' - light_ob.data.renderman.renderman_lock_light_type = True - light_ob.data.use_nodes = True - light_ob.data.renderman.use_renderman_node = True - light_ob.name = self.rman_light_name - light_ob.data.name = self.rman_light_name + light = bpy.data.lights.new(self.rman_light_name, 'AREA') + light_ob = bpy.data.objects.new(self.rman_light_name, light) + + light.renderman.renderman_light_role = 'RMAN_LIGHT' + light.renderman.renderman_lock_light_type = True + light.use_nodes = True + light.renderman.use_renderman_node = True + shadergraph_utils.hide_cycles_nodes(light) - nt = light_ob.data.node_tree + nt = light.node_tree output = nt.nodes.new('RendermanOutputNode') default = nt.nodes.new('%sLightNode' % self.rman_light_name) default.location = output.location @@ -208,7 +214,13 @@ def execute(self, context): output.inputs[0].hide = True output.inputs[2].hide = True output.inputs[3].hide = True - light_ob.data.renderman.renderman_light_shader = self.rman_light_name + light.renderman.renderman_light_shader = self.rman_light_name + + context.scene.collection.objects.link(light_ob) + if context.view_layer.objects.active: + context.view_layer.objects.active.select_set(False) + light_ob.select_set(True) + context.view_layer.objects.active = light_ob return {"FINISHED"} @@ -229,20 +241,17 @@ def description(cls, context, properties): info = get_description('lightfilter', properties.rman_lightfilter_name) return info - def execute(self, context): - selected_objects = context.selected_objects + def create_lightfilter(self, context): + light_filter = bpy.data.lights.new(self.rman_lightfilter_name, 'AREA') + light_filter_ob = bpy.data.objects.new(self.rman_lightfilter_name, light_filter) - bpy.ops.object.light_add(type='AREA') - light_filter_ob = context.object - light_filter_ob.data.renderman.renderman_light_role = 'RMAN_LIGHTFILTER' - light_filter_ob.data.renderman.renderman_lock_light_type = True - light_filter_ob.data.use_nodes = True - light_filter_ob.data.renderman.use_renderman_node = True - - light_filter_ob.name = self.rman_lightfilter_name - light_filter_ob.data.name = self.rman_lightfilter_name + light_filter.renderman.renderman_light_role = 'RMAN_LIGHTFILTER' + light_filter.renderman.renderman_lock_light_type = True + light_filter.use_nodes = True + light_filter.renderman.use_renderman_node = True + shadergraph_utils.hide_cycles_nodes(light_filter) - nt = light_filter_ob.data.node_tree + nt = light_filter.node_tree output = nt.nodes.new('RendermanOutputNode') default = nt.nodes.new('%sLightfilterNode' % self.rman_lightfilter_name) default.location = output.location @@ -251,19 +260,44 @@ def execute(self, context): output.inputs[0].hide = True output.inputs[1].hide = True output.inputs[2].hide = True - light_filter_ob.data.renderman.renderman_light_filter_shader = self.rman_lightfilter_name + light_filter.renderman.renderman_light_filter_shader = self.rman_lightfilter_name + context.scene.collection.objects.link(light_filter_ob) + if context.view_layer.objects.active: + context.view_layer.objects.active.select_set(False) + light_filter_ob.select_set(True) + context.view_layer.objects.active = light_filter_ob + + return light_filter_ob + + def execute(self, context): + selected_objects = context.selected_objects if self.properties.add_to_selected: - for ob in selected_objects: - rman_type = object_utils._detect_primitive_(ob) - if rman_type == 'LIGHT': - light_filter_item = ob.data.renderman.light_filters.add() - light_filter_item.linked_filter_ob = light_filter_ob - elif shadergraph_utils.is_mesh_light(ob): - mat = ob.active_material - if mat: - light_filter_item = mat.renderman_light.light_filters.add() + if not selected_objects: + light_filter_ob = self.create_lightfilter(context) + else: + light_filter_ob = None + do_parent = prefs_utils.get_pref('rman_parent_lightfilter') + if not do_parent: + light_filter_ob = self.create_lightfilter(context) + for ob in selected_objects: + rman_type = object_utils._detect_primitive_(ob) + if rman_type == 'LIGHT': + if do_parent: + light_filter_ob = self.create_lightfilter(context) + light_filter_ob.parent = ob + light_filter_item = ob.data.renderman.light_filters.add() light_filter_item.linked_filter_ob = light_filter_ob + elif shadergraph_utils.is_mesh_light(ob): + mat = ob.active_material + if mat: + if do_parent: + light_filter_ob = self.create_lightfilter(context) + light_filter_ob.parent = ob + light_filter_item = mat.renderman_light.light_filters.add() + light_filter_item.linked_filter_ob = light_filter_ob + else: + light_filter_ob = self.create_lightfilter(context) return {"FINISHED"} @@ -287,34 +321,22 @@ def execute(self, context): selection = bpy.context.selected_objects if hasattr( bpy.context, 'selected_objects') else [] bxdf_name = self.properties.bxdf_name - mat = bpy.data.materials.new(bxdf_name) - - mat.use_nodes = True - nt = mat.node_tree - - output = nt.nodes.new('RendermanOutputNode') - default = nt.nodes.new('%sBxdfNode' % bxdf_name) - default.location = output.location - default.location[0] -= 300 - nt.links.new(default.outputs[0], output.inputs[0]) - output.inputs[1].hide = True - output.inputs[3].hide = True - default.update_mat(mat) - - if bxdf_name == 'PxrLayerSurface': - shadergraph_utils.create_pxrlayer_nodes(nt, default) - + mat = shadergraph_utils.create_bxdf(bxdf_name) for obj in selection: if(obj.type not in EXCLUDED_OBJECT_TYPES): if obj.type == 'EMPTY': obj.renderman.rman_material_override = mat else: material_slots = getattr(obj, 'material_slots', None) - if material_slots: + if material_slots is None: + continue + if len(material_slots) < 1: obj.active_material = mat else: - bpy.ops.object.material_slot_add() - obj.active_material = mat + material_slot = material_slots[0] + material_slot.material = mat + obj.active_material_index = 0 + obj.active_material = mat return {"FINISHED"} class PRMAN_OT_RM_Create_MeshLight(bpy.types.Operator): @@ -325,9 +347,9 @@ class PRMAN_OT_RM_Create_MeshLight(bpy.types.Operator): def create_mesh_light_material(self, context): mat = bpy.data.materials.new("PxrMeshLight") - mat.use_nodes = True nt = mat.node_tree + shadergraph_utils.hide_cycles_nodes(mat) output = nt.nodes.new('RendermanOutputNode') geoLight = nt.nodes.new('PxrMeshLightLightNode') @@ -359,11 +381,16 @@ def execute(self, context): continue mat = self.create_mesh_light_material(context) material_slots = getattr(obj, 'material_slots', None) - if material_slots: + + if material_slots is None: + continue + if len(material_slots) < 1: obj.active_material = mat else: - bpy.ops.object.material_slot_add() - obj.active_material = mat + material_slot = material_slots[0] + material_slot.material = mat + obj.active_material_index = 0 + obj.active_material = mat return {"FINISHED"} @@ -503,7 +530,6 @@ def execute(self, context): scene = context.scene rm = scene.renderman output_dir = string_utils.expand_string(rm.path_rib_output, - frame=scene.frame_current, asFilePath=True) output_dir = os.path.dirname(output_dir) bpy.ops.wm.url_open( diff --git a/rman_operators/rman_operators_volumes.py b/rman_operators/rman_operators_volumes.py index 8d40ea3d..c5d9a3bc 100644 --- a/rman_operators/rman_operators_volumes.py +++ b/rman_operators/rman_operators_volumes.py @@ -86,6 +86,8 @@ def add_selected(self, context): scene = context.scene rm = scene.renderman vol_aggregates_index = rm.vol_aggregates_index + if vol_aggregates_index == 0: + return {'FINISHED'} ob = getattr(context, "selected_obj", None) if not ob: return {'FINISHED'} @@ -157,7 +159,7 @@ def execute(self, context): for i, member in enumerate(vol_aggregate.members): if member.ob_pointer == ob: vol_aggregate.members.remove(i) - ob.update_tag(refresh={'OBJECT'}) + ob.update_tag(refresh={'DATA'}) break return {'FINISHED'} diff --git a/rman_presets/__init__.py b/rman_presets/__init__.py index d32e1dba..aae093c1 100644 --- a/rman_presets/__init__.py +++ b/rman_presets/__init__.py @@ -23,30 +23,20 @@ # # ##### END MIT LICENSE BLOCK ##### +import bpy from . import properties -from . import ui +if not bpy.app.background: + from . import ui from . import operators -from ..rfb_utils.prefs_utils import get_pref -import os def register(): properties.register() - if get_pref('rman_ui_framework') == 'QT': - try: - from . import qt_app - qt_app.register() - except: - pass - ui.register() + if not bpy.app.background: + ui.register() operators.register() def unregister(): properties.unregister() - if get_pref('rman_ui_framework') == 'QT': - try: - from . import qt_app - qt_app.unregister() - except: - pass - ui.unregister() + if not bpy.app.background: + ui.unregister() operators.unregister() \ No newline at end of file diff --git a/rman_presets/core.py b/rman_presets/core.py index e959b52d..6db9e656 100644 --- a/rman_presets/core.py +++ b/rman_presets/core.py @@ -573,31 +573,32 @@ def export_material_preset(mat, nodes_to_convert, renderman_output_node, Asset): nodeClass = 'root' rmanNode = 'shadingEngine' nodeType = 'shadingEngine' - nodeName = '%s_SG' % Asset.label() + sg_name = Asset.label().replace('.', '_') + nodeName = '%s_SG' % sg_name Asset.addNode(nodeName, nodeType, nodeClass, rmanNode, externalosl=False) - if renderman_output_node.inputs['Bxdf'].is_linked: + if renderman_output_node.inputs['bxdf_in'].is_linked: infodict = {} infodict['name'] = 'rman__surface' infodict['type'] = 'reference float3' infodict['value'] = None Asset.addParam(nodeName, nodeType, 'rman__surface', infodict) - from_node = renderman_output_node.inputs['Bxdf'].links[0].from_node + from_node = renderman_output_node.inputs['bxdf_in'].links[0].from_node srcPlug = "%s.%s" % (fix_blender_name(from_node.name), 'outColor') dstPlug = "%s.%s" % (nodeName, 'rman__surface') Asset.addConnection(srcPlug, dstPlug) - if renderman_output_node.inputs['Displacement'].is_linked: + if renderman_output_node.inputs['displace_in'].is_linked: infodict = {} infodict['name'] = 'rman__displacement' infodict['type'] = 'reference float3' infodict['value'] = None Asset.addParam(nodeName, nodeType, 'rman__displacement', infodict) - from_node = renderman_output_node.inputs['Displacement'].links[0].from_node + from_node = renderman_output_node.inputs['displace_in'].links[0].from_node srcPlug = "%s.%s" % (fix_blender_name(from_node.name), 'outColor') dstPlug = "%s.%s" % (nodeName, 'rman__displacement') Asset.addConnection(srcPlug, dstPlug) @@ -1058,6 +1059,7 @@ def createNodes(Asset): mat = bpy.data.materials.new(Asset.label()) mat.use_nodes = True nt = mat.node_tree + shadergraph_utils.hide_cycles_nodes(mat) # create output node output_node = nt.nodes.new('RendermanOutputNode') @@ -1272,8 +1274,8 @@ def import_light_rig(Asset): def connectNodes(Asset, nt, nodeDict): output = shadergraph_utils.find_node_from_nodetree(nt, 'RendermanOutputNode') - bxdf_socket = output.inputs['Bxdf'] - displace_socket = output.inputs['Displacement'] + bxdf_socket = output.inputs['bxdf_in'] + displace_socket = output.inputs['displace_in'] for con in Asset.connectionList(): #print('+ %s.%s -> %s.%s' % (nodeDict[con.srcNode()](), con.srcParam(), @@ -1298,12 +1300,12 @@ def connectNodes(Asset, nt, nodeDict): elif output == dstNode: # check if this is a root node connection if dstSocket == 'surfaceShader' or dstSocket == 'rman__surface': - nt.links.new(srcNode.outputs['Bxdf'], output.inputs['Bxdf']) + nt.links.new(srcNode.outputs['bxdf_out'], output.inputs['bxdf_in']) elif dstSocket == 'displacementShader' or dstSocket == 'rman__displacement': - nt.links.new(srcNode.outputs['Displacement'], output.inputs['Displacement']) + nt.links.new(srcNode.outputs['displace_out'], output.inputs['displace_in']) elif renderman_node_type == 'bxdf': # this is a regular upstream bxdf connection - nt.links.new(srcNode.outputs['Bxdf'], dstNode.inputs[dstSocket]) + nt.links.new(srcNode.outputs['bxdf_out'], dstNode.inputs[dstSocket]) else: rfb_log().debug('error connecting %s.%s to %s.%s' % (srcNode.name,srcSocket, dstNode.name, dstSocket)) @@ -1315,16 +1317,16 @@ def connectNodes(Asset, nt, nodeDict): for node in nt.nodes: renderman_node_type = getattr(node, 'renderman_node_type', '') if renderman_node_type == 'bxdf': - if not node.outputs['Bxdf'].is_linked: + if not node.outputs['bxdf_out'].is_linked: bxdf_candidate = node elif renderman_node_type == 'displace': displace_candidate = node if bxdf_candidate: - nt.links.new(bxdf_candidate.outputs['Bxdf'], output.inputs['Bxdf']) + nt.links.new(bxdf_candidate.outputs['bxdf_out'], output.inputs['bxdf_in']) if not displace_socket.is_linked and displace_candidate: - nt.links.new(displace_candidate.outputs['Displacement'], output.inputs['Displacement']) + nt.links.new(displace_candidate.outputs['displace_out'], output.inputs['displace_in']) def create_displayfilter_nodes(Asset): @@ -1350,7 +1352,7 @@ def create_displayfilter_nodes(Asset): created_node.name = node_id created_node.label = node_id output.add_input() - nt.links.new(created_node.outputs['DisplayFilter'], output.inputs[-1]) + nt.links.new(created_node.outputs['displayfilter_out'], output.inputs[-1]) nodeDict[node_id] = created_node.name setParams(Asset, created_node, df_node.paramsDict()) @@ -1464,7 +1466,7 @@ def export_asset(nodes, atype, infodict, category, cfg, renderPreview='std', # prmanversion = envconfig().build_info.version() Asset.setCompatibility(hostName='Blender', - hostVersion=bpy.app.version, + hostVersion=bpy.app.version_string, rendererVersion=prmanversion) # parse scene @@ -1507,4 +1509,4 @@ def export_asset(nodes, atype, infodict, category, cfg, renderPreview='std', # # print("exportAsset: %s..." % dirPath) Asset.save(jsonfile, compact=False) - return True \ No newline at end of file + return True diff --git a/rman_presets/operators.py b/rman_presets/operators.py index a4d0febe..5b3af2b5 100644 --- a/rman_presets/operators.py +++ b/rman_presets/operators.py @@ -678,16 +678,14 @@ def poll(cls, context): current_category_path = hostPrefs.getSelectedCategory() if current_category_path == '': return False - rel_path = os.path.relpath(current_category_path, hostPrefs.getSelectedLibrary()) - if rel_path in ['EnvironmentMaps', 'Materials', 'LightRigs']: + if current_category_path in ['EnvironmentMaps', 'Materials', 'LightRigs']: return False return True def execute(self, context): hostPrefs = rab.get_host_prefs() current_category_path = hostPrefs.getSelectedCategory() - rel_path = os.path.relpath(current_category_path, hostPrefs.getSelectedLibrary()) - ral.deleteCategory(hostPrefs.cfg, rel_path) + ral.deleteCategory(hostPrefs.cfg, current_category_path) self.op = getattr(context, 'op_ptr', None) if self.op: self.op.dummy_index = -1 diff --git a/rman_presets/qt_app.py b/rman_presets/qt_app.py deleted file mode 100644 index 350e88ae..00000000 --- a/rman_presets/qt_app.py +++ /dev/null @@ -1,72 +0,0 @@ -try: - from ..rman_ui import rfb_qt -except: - raise - -import sys -import bpy - -from ..rfb_logger import rfb_log -from . import core as bl_pb_core - -__PRESET_BROWSER_WINDOW__ = None - -class PresetBrowserQtAppTimed(rfb_qt.RfbBaseQtAppTimed): - bl_idname = "wm.rpb_qt_app_timed" - bl_label = "RenderManPreset Browser" - - def __init__(self): - super(PresetBrowserQtAppTimed, self).__init__() - - def execute(self, context): - self._window = PresetBrowserWrapper() - return super(PresetBrowserQtAppTimed, self).execute(context) - -class PresetBrowserWrapper(rfb_qt.RmanQtWrapper): - - def __init__(self): - super(PresetBrowserWrapper, self).__init__() - # import here because we will crash Blender - # when we try to import it globally - import rman_utils.rman_assets.ui as rui - - self.resize(1024, 1024) - self.setWindowTitle('RenderMan Preset Browser') - - self.hostPrefs = bl_pb_core.get_host_prefs() - self.ui = rui.Ui(self.hostPrefs, parent=self) - self.setLayout(self.ui.topLayout) - self.show() # Show window - - def closeEvent(self, event): - self.hostPrefs.saveAllPrefs() - event.accept() - -class PRMAN_OT_Renderman_Presets_Editor(bpy.types.Operator): - bl_idname = "renderman.rman_open_presets_editor" - bl_label = "PresetBrowser" - - def execute(self, context): - - global __PRESET_BROWSER_WINDOW__ - if sys.platform == "darwin": - rfb_qt.run_with_timer(__PRESET_BROWSER_WINDOW__, PresetBrowserWrapper) - else: - bpy.ops.wm.rpb_qt_app_timed() - - return {'RUNNING_MODAL'} - -classes = [ - PRMAN_OT_Renderman_Presets_Editor, - PresetBrowserQtAppTimed -] - -def register(): - from ..rfb_utils import register_utils - - register_utils.rman_register_classes(classes) - -def unregister(): - from ..rfb_utils import register_utils - - register_utils.rman_unregister_classes(classes) \ No newline at end of file diff --git a/rman_presets/rmanAssetsBlender.py b/rman_presets/rmanAssetsBlender.py index df875b71..b8235499 100644 --- a/rman_presets/rmanAssetsBlender.py +++ b/rman_presets/rmanAssetsBlender.py @@ -194,7 +194,7 @@ def bl_export_asset(nodes, atype, infodict, category, cfg, renderPreview='std', # prmanversion = envconfig().build_info.version() Asset.setCompatibility(hostName='Blender', - hostVersion=bpy.app.version, + hostVersion=bpy.app.version_string, rendererVersion=prmanversion) # parse maya scene @@ -232,4 +232,4 @@ def bl_export_asset(nodes, atype, infodict, category, cfg, renderPreview='std', Asset.save(jsonfile, compact=False) ral.renderAssetPreview(Asset, progress=None, rmantree=envconfig().rmantree) - return True \ No newline at end of file + return True diff --git a/rman_presets/ui.py b/rman_presets/ui.py index 8e3de1b4..e8a09a94 100644 --- a/rman_presets/ui.py +++ b/rman_presets/ui.py @@ -23,27 +23,64 @@ # # ##### END MIT LICENSE BLOCK ##### -from ..rfb_utils.prefs_utils import get_pref, get_addon_prefs +from ..rfb_utils.prefs_utils import get_pref, get_addon_prefs, using_qt from ..rfb_logger import rfb_log from ..rman_config import __RFB_CONFIG_DICT__ as rfb_config +from ..rman_ui import rfb_qt # for panel icon from .. import rfb_icons from . import icons as rpb_icons import bpy +import sys from .properties import RendermanPreset, RendermanPresetCategory from bpy.props import * # for previews of assets from . import icons from . import rmanAssetsBlender as rab +from . import core as bl_pb_core from rman_utils.rman_assets import core as ra from rman_utils.rman_assets.common.exceptions import RmanAssetError from bpy.props import StringProperty, IntProperty import os +__PRESET_BROWSER_WINDOW__ = None + +class PresetBrowserQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.rpb_qt_app_timed" + bl_label = "RenderManPreset Browser" + + def __init__(self): + super(PresetBrowserQtAppTimed, self).__init__() + + def execute(self, context): + global __PRESET_BROWSER_WINDOW__ + __PRESET_BROWSER_WINDOW__ = PresetBrowserWrapper() + self._window = __PRESET_BROWSER_WINDOW__ + return super(PresetBrowserQtAppTimed, self).execute(context) + +class PresetBrowserWrapper(rfb_qt.RmanQtWrapper): + + def __init__(self): + super(PresetBrowserWrapper, self).__init__() + # import here because we will crash Blender + # when we try to import it globally + import rman_utils.rman_assets.ui as rui + + self.resize(1024, 1024) + self.setWindowTitle('RenderMan Preset Browser') + + self.hostPrefs = bl_pb_core.get_host_prefs() + self.ui = rui.Ui(self.hostPrefs, parent=self) + self.setLayout(self.ui.topLayout) + + def closeEvent(self, event): + self.hostPrefs.saveAllPrefs() + event.accept() + # panel for the toolbar of node editor class PRMAN_PT_Renderman_Presets_UI_Panel(bpy.types.Panel): @@ -449,6 +486,18 @@ def __init__(self): def invoke(self, context, event): + if using_qt(): + global __PRESET_BROWSER_WINDOW__ + if __PRESET_BROWSER_WINDOW__ and __PRESET_BROWSER_WINDOW__.isVisible(): + return {'FINISHED'} + + if sys.platform == "darwin": + __PRESET_BROWSER_WINDOW__ = rfb_qt.run_with_timer(__PRESET_BROWSER_WINDOW__, PresetBrowserWrapper) + else: + bpy.ops.wm.rpb_qt_app_timed() + + return {'FINISHED'} + self.load_categories(context) self.load_presets(context) @@ -474,7 +523,9 @@ def rman_presets_object_menu(self, context): VIEW3D_MT_renderman_presets_object_context_menu, PRMAN_MT_renderman_preset_ops_menu, RENDERMAN_UL_Presets_Categories_List, - RENDERMAN_UL_Presets_Preset_List + RENDERMAN_UL_Presets_Preset_List, + PRMAN_OT_Renderman_Presets_Editor, + PresetBrowserQtAppTimed ] @@ -482,16 +533,6 @@ def register(): from ..rfb_utils import register_utils register_utils.rman_register_classes(classes) - - if get_pref('rman_ui_framework') != 'QT': - register_utils.rman_register_class(PRMAN_OT_Renderman_Presets_Editor) - else: - try: - from PySide2 import QtCore, QtWidgets - except: - # can't find PySide2, load old preset browser - register_utils.rman_register_class(PRMAN_OT_Renderman_Presets_Editor) - bpy.types.VIEW3D_MT_add.prepend(rman_presets_object_menu) bpy.types.VIEW3D_MT_object_context_menu.prepend(rman_presets_object_menu) @@ -502,4 +543,4 @@ def unregister(): bpy.types.VIEW3D_MT_add.remove(rman_presets_object_menu) bpy.types.VIEW3D_MT_object_context_menu.remove(rman_presets_object_menu) - register_utils.rman_unregister_class(PRMAN_OT_Renderman_Presets_Editor) \ No newline at end of file + register_utils.rman_unregister_classes(classes) \ No newline at end of file diff --git a/rman_properties/rman_properties_misc/__init__.py b/rman_properties/rman_properties_misc/__init__.py index ed8a68ee..789e1870 100644 --- a/rman_properties/rman_properties_misc/__init__.py +++ b/rman_properties/rman_properties_misc/__init__.py @@ -3,7 +3,9 @@ CollectionProperty, BoolVectorProperty, IntVectorProperty from ...rfb_utils import shadergraph_utils +from ...rfb_utils import scene_utils from ...rfb_logger import rfb_log +from ...rfb_utils.prefs_utils import using_qt from ... import rman_config import bpy @@ -61,47 +63,28 @@ def update_name(self, context): name: StringProperty(name="name", update=update_name) def update_ob_pointer(self, context): - self.ob_pointer.update_tag(refresh={'OBJECT'}) + if not using_qt(): + self.ob_pointer.update_tag(refresh={'OBJECT'}) ob_pointer: PointerProperty(type=bpy.types.Object, update=update_ob_pointer) def update_link(self, context): light_ob = getattr(context, 'light_ob', None) - if not light_ob: + if not light_ob or light_ob.type != 'LIGHT': + return + ''' + if not light_ob and hasattr(context, 'active_object'): light_ob = context.active_object - if light_ob.type != 'LIGHT': + if light_ob and light_ob.type != 'LIGHT': return - - light_props = shadergraph_utils.get_rman_light_properties_group(light_ob) - if light_props.renderman_light_role not in {'RMAN_LIGHTFILTER', 'RMAN_LIGHT'}: + if not light_ob: return - - light_ob.update_tag(refresh={'DATA'}) + ''' ob = self.ob_pointer - light_props = shadergraph_utils.get_rman_light_properties_group(light_ob) - if light_props.renderman_light_role == 'RMAN_LIGHT': - if self.illuminate == 'OFF': - subset = ob.renderman.rman_lighting_excludesubset.add() - subset.name = light_ob.name - subset.light_ob = light_ob - else: - for j, subset in enumerate(ob.renderman.rman_lighting_excludesubset): - if subset.light_ob == light_ob: - ob.renderman.rman_lighting_excludesubset.remove(j) - break - else: - if self.illuminate == 'OFF': - for j, subset in enumerate(ob.renderman.rman_lightfilter_subset): - if subset.light_ob == light_ob: - ob.renderman.rman_lightfilter_subset.remove(j) - break - else: - subset = ob.renderman.rman_lightfilter_subset.add() - subset.name = light_ob.name - subset.light_ob = light_ob - - ob.update_tag(refresh={'OBJECT'}) + if scene_utils.set_lightlinking_properties(ob, light_ob, self.illuminate): + if not using_qt(): + ob.update_tag(refresh={'DATA'}) illuminate: EnumProperty( name="Illuminate", @@ -249,24 +232,6 @@ class RendermanArrayGroup(bpy.types.PropertyGroup): value_normal: FloatVectorProperty(name="Value", default=(0.0,0.0,0.0), size=3, subtype="NONE") value_point: FloatVectorProperty(name="Value", default=(0.0,0.0,0.0), size=3, subtype="XYZ") -class RendermanAnimSequenceSettings(bpy.types.PropertyGroup): - animated_sequence: BoolProperty( - name="Animated Sequence", - description="Interpret this archive as an animated sequence (converts #### in file path to frame number)", - default=False) - sequence_in: IntProperty( - name="Sequence In Point", - description="The first numbered file to use", - default=1) - sequence_out: IntProperty( - name="Sequence Out Point", - description="The last numbered file to use", - default=24) - blender_start: IntProperty( - name="Blender Start Frame", - description="The frame in Blender to begin playing back the sequence", - default=1) - class Tab_CollectionGroup(bpy.types.PropertyGroup): ################# @@ -325,7 +290,6 @@ class Tab_CollectionGroup(bpy.types.PropertyGroup): LightLinking, RendermanMeshPrimVar, RendermanReferencePosePrimVars, - RendermanAnimSequenceSettings, Tab_CollectionGroup, RENDERMAN_UL_Array_List, RendermanArrayGroup diff --git a/rman_properties/rman_properties_object/__init__.py b/rman_properties/rman_properties_object/__init__.py index df9f0fb9..fe9768b6 100644 --- a/rman_properties/rman_properties_object/__init__.py +++ b/rman_properties/rman_properties_object/__init__.py @@ -4,7 +4,6 @@ from ... import rman_config from ...rman_config import RmanBasePropertyGroup -from ..rman_properties_misc import RendermanAnimSequenceSettings from ..rman_properties_misc import RendermanLightPointer from ...rfb_utils import shadergraph_utils from ...rfb_utils import object_utils @@ -43,11 +42,7 @@ class RendermanObjectSettings(RmanBasePropertyGroup, bpy.types.PropertyGroup): rman_config_name: StringProperty(name='rman_config_name', default='rman_properties_object') - - archive_anim_settings: PointerProperty( - type=RendermanAnimSequenceSettings, - name="Animation Sequence Settings") - + hide_primitive_type: BoolProperty( name="Hide Primitive Type", default=False @@ -111,14 +106,22 @@ def update_solo(self, context): user_attributes_index: IntProperty(min=-1, default=-1) def get_object_type(self): - if bpy.context.object: - return object_utils._detect_primitive_(bpy.context.object) + if self.id_data: + return object_utils._detect_primitive_(self.id_data) return "" bl_object_type: StringProperty( get=get_object_type ) + def get_bake_mode(self): + scene = bpy.context.scene + return scene.renderman.rman_bake_mode + + rman_bake_mode: StringProperty( + get=get_bake_mode + ) + rman_config_classes = [ RendermanObjectSettings ] diff --git a/rman_properties/rman_properties_scene/__init__.py b/rman_properties/rman_properties_scene/__init__.py index 832da0ac..11b49c84 100644 --- a/rman_properties/rman_properties_scene/__init__.py +++ b/rman_properties/rman_properties_scene/__init__.py @@ -3,7 +3,7 @@ CollectionProperty from ...rfb_utils.envconfig_utils import envconfig -from ...rfb_utils.prefs_utils import get_pref +from ...rfb_utils.prefs_utils import get_pref, using_qt from ...rfb_logger import rfb_log from ... import rman_bl_nodes from ...rman_bl_nodes import rman_bl_nodes_props @@ -132,7 +132,7 @@ def get_is_rman_viewport_rendering(self): return (rman_render.rman_is_viewport_rendering or is_shading) def get_light_linking_inverted(self): - return get_pref('rman_invert_light_linking') + return get_pref('rman_invert_light_linking') and not using_qt() current_platform: StringProperty(get=get_platform) is_ncr_license: BoolProperty(get=get_is_ncr_license) diff --git a/rman_render.py b/rman_render.py index 56f33d20..488e1ec9 100644 --- a/rman_render.py +++ b/rman_render.py @@ -1,6 +1,7 @@ import time import os import rman +import ice import bpy import sys from .rman_constants import RFB_VIEWPORT_MAX_BUCKETS, RMAN_RENDERMAN_BLUE @@ -25,7 +26,9 @@ from .rfb_utils import string_utils from .rfb_utils import display_utils from .rfb_utils import scene_utils +from .rfb_utils import transform_utils from .rfb_utils.prefs_utils import get_pref +from .rfb_utils.timer_utils import time_this # config from .rman_config import __RFB_CONFIG_DICT__ as rfb_config @@ -33,25 +36,45 @@ # roz stats from .rman_stats import RfBStatsManager +# handlers +from .rman_handlers.rman_it_handlers import add_ipr_to_it_handlers, remove_ipr_to_it_handlers + __RMAN_RENDER__ = None __RMAN_IT_PORT__ = -1 __BLENDER_DSPY_PLUGIN__ = None __DRAW_THREAD__ = None __RMAN_STATS_THREAD__ = None -def __turn_off_viewport__(): - ''' - Loop through all of the windows/areas and turn shading to SOLID - for all view_3d areas. - ''' - rfb_log().debug("Attempting to turn off viewport render") - for window in bpy.context.window_manager.windows: - for area in window.screen.areas: - if area.type == 'VIEW_3D': - for space in area.spaces: - if space.type == 'VIEW_3D': - space.shading.type = 'SOLID' - area.tag_redraw() +# map Blender display file format +# to ice format +__BLENDER_TO_ICE_DSPY__ = { + 'TIFF': ice.constants.FMT_TIFFFLOAT, + 'TARGA': ice.constants.FMT_TGA, + 'TARGA_RAW': ice.constants.FMT_TGA, + 'JPEG': ice.constants.FMT_JPEG, + 'JPEG2000': ice.constants.FMT_JPEG, + 'OPEN_EXR': ice.constants.FMT_EXRFLOAT, + 'CINEON': ice.constants.FMT_CINEON, + 'PNG': ice.constants.FMT_PNG +} + +# map ice format to a file extension +__ICE_EXT_MAP__ = { + ice.constants.FMT_TIFFFLOAT: 'tif', + ice.constants.FMT_TGA: 'tga', + ice.constants.FMT_JPEG: 'jpg', + ice.constants.FMT_EXRFLOAT: 'exr', + ice.constants.FMT_CINEON: 'cin', + ice.constants.FMT_PNG: 'png' +} + +# map rman display to ice format +__RMAN_TO_ICE_DSPY__ = { + 'tiff': ice.constants.FMT_TIFFFLOAT, + 'targa': ice.constants.FMT_TGA, + 'openexr': ice.constants.FMT_EXRFLOAT, + 'png': ice.constants.FMT_PNG +} def __update_areas__(): for window in bpy.context.window_manager.windows: @@ -71,7 +94,7 @@ def __draw_callback__(): return False DRAWCALLBACK_FUNC = ctypes.CFUNCTYPE(ctypes.c_bool) -__CALLBACK_FUNC__ = DRAWCALLBACK_FUNC(__draw_callback__) +__CALLBACK_FUNC__ = DRAWCALLBACK_FUNC(__draw_callback__) class ItHandler(chatserver.ItBaseHandler): @@ -93,7 +116,7 @@ def stopRender(self): global __RMAN_RENDER__ rfb_log().debug("Stop Render Requested.") if __RMAN_RENDER__.rman_interactive_running: - __turn_off_viewport__() + __RMAN_RENDER__.stop_render(stop_draw_thread=False) __RMAN_RENDER__.del_bl_engine() def selectObjectById(self): @@ -156,11 +179,11 @@ def start_cmd_server(): def draw_threading_func(db): refresh_rate = get_pref('rman_viewport_refresh_rate', default=0.01) while db.rman_is_live_rendering: - if not scene_utils.any_areas_shading(): - # if there are no 3d viewports, stop IPR + if db.bl_viewport.shading.type != 'RENDERED': + # if the viewport is not rendering, stop IPR db.del_bl_engine() break - if db.rman_is_xpu: + if db.xpu_slow_mode: if db.has_buffer_updated(): try: db.bl_engine.tag_redraw() @@ -227,29 +250,153 @@ def live_render_cb(e, d, db): else: db.rman_is_refining = True -def preload_xpu(): +def preload_dsos(rman_render): """On linux there is a problem with std::call_once and blender, by default, being linked with a static libstdc++. The loader seems to not be able to get the right tls key - for the __once_call global when libprman loads libxpu. By preloading + for the __once_call global when libprman loads libxpu etc. By preloading we end up calling the proxy in the blender executable and that works. + + Arguments: + rman_render (RmanRender) - instance of RmanRender where we want to store + the ctypes.CDLL - Returns: - ctypes.CDLL of xpu or None if that fails. None if not on linux - """ + """ if sys.platform != 'linux': - return None + return - tree = envconfig().rmantree - xpu_path = os.path.join(tree, 'lib', 'libxpu.so') + plugins = [ + 'lib/libxpu.so', + 'lib/plugins/impl_openvdb.so', + ] - try: - xpu = ctypes.CDLL(xpu_path) - return xpu - except OSError as error: - rfb_log().debug('Failed to preload xpu: {0}'.format(error)) - return None + tree = envconfig().rmantree + + for plugin in plugins: + plugin_path = os.path.join(tree, plugin) + try: + rman_render.preloaded_dsos.append(ctypes.CDLL(plugin_path)) + except OSError as error: + rfb_log().debug('Failed to preload {0}: {1}'.format(plugin_path, error)) + + +class BlRenderResultHelper: + def __init__(self, rman_render, bl_scene, dspy_dict): + self.rman_render = rman_render + self.bl_scene = bl_scene + self.dspy_dict = dspy_dict + self.width = -1 + self.height = -1 + self.size_x = -1 + self.size_y = -1 + self.bl_result = None + self.bl_image_rps = dict() + self.render = None + self.render_view = None + self.image_scale = -1 + self.write_aovs = False + + def register_passes(self): + self.render = self.rman_render.rman_scene.bl_scene.render + self.render_view = self.rman_render.bl_engine.active_view_get() + self.image_scale = self.render.resolution_percentage / 100.0 + self.width = int(self.render.resolution_x * self.image_scale) + self.height = int(self.render.resolution_y * self.image_scale) + + # register any AOV's as passes + for i, dspy_nm in enumerate(self.dspy_dict['displays'].keys()): + if i == 0: + continue + + num_channels = -1 + while num_channels == -1: + num_channels = self.rman_render.get_numchannels(i) + + dspy = self.dspy_dict['displays'][dspy_nm] + dspy_chan = dspy['params']['displayChannels'][0] + chan_info = self.dspy_dict['channels'][dspy_chan] + chan_type = chan_info['channelType']['value'] + + if num_channels == 4: + self.rman_render.bl_engine.add_pass(dspy_nm, 4, 'RGBA') + elif num_channels == 3: + if chan_type == 'color': + self.rman_render.bl_engine.add_pass(dspy_nm, 3, 'RGB') + else: + self.rman_render.bl_engine.add_pass(dspy_nm, 3, 'XYZ') + elif num_channels == 2: + self.rman_render.bl_engine.add_pass(dspy_nm, 2, 'XY') + else: + self.rman_render.bl_engine.add_pass(dspy_nm, 1, 'X') + + self.size_x = self.width + self.size_y = self.height + if self.render.use_border: + self.size_x = int(self.width * (self.render.border_max_x - self.render.border_min_x)) + self.size_y = int(self.height * (self.render.border_max_y - self.render.border_min_y)) + + self.bl_result = self.rman_render.bl_engine.begin_result(0, 0, + self.size_x, + self.size_y, + view=self.render_view) + + for i, dspy_nm in enumerate(self.dspy_dict['displays'].keys()): + if i == 0: + render_pass = self.bl_result.layers[0].passes.find_by_name("Combined", self.render_view) + else: + render_pass = self.bl_result.layers[0].passes.find_by_name(dspy_nm, self.render_view) + self.bl_image_rps[i] = render_pass + + def update_passes(self): + for i, rp in self.bl_image_rps.items(): + buffer = self.rman_render._get_buffer(self.width, self.height, image_num=i, + num_channels=rp.channels, + as_flat=False, + back_fill=False, + render=self.render) + if buffer is None: + continue + rp.rect = buffer + + if self.rman_render.bl_engine: + self.rman_render.bl_engine.update_result(self.bl_result) + + def finish_passes(self): + if self.bl_result: + if self.rman_render.bl_engine: + self.rman_render.bl_engine.end_result(self.bl_result) + + # check if we should write out the AOVs + if self.write_aovs: + use_ice = hasattr(ice, 'FromArray') + for i, dspy_nm in enumerate(self.dspy_dict['displays'].keys()): + filepath = self.dspy_dict['displays'][dspy_nm]['filePath'] + + if i == 0: + continue + + # write out the beauty with a 'raw' substring + toks = os.path.splitext(filepath) + filepath = '%s_beauty_raw.exr' % (toks[0]) + + if use_ice: + buffer = self.rman_render._get_buffer(self.width, self.height, image_num=i, raw_buffer=True, as_flat=False) + if buffer is None: + continue + + # use ice to save out the image + img = ice.FromArray(buffer) + img = img.Flip(False, True, False) + img_format = ice.constants.FMT_EXRFLOAT + if not display_utils.using_rman_displays(): + img_format = __BLENDER_TO_ICE_DSPY__.get(self.bl_scene.render.image_settings.file_format, img_format) + + # change file extension + toks = os.path.splitext(filepath) + ext = __ICE_EXT_MAP__.get(img_format) + filepath = '%s.%s' % (toks[0], ext) + img.Save(filepath, img_format) class RmanRender(object): ''' @@ -288,11 +435,15 @@ def __init__(self): self.stats_mgr = RfBStatsManager(self) self.deleting_bl_engine = threading.Lock() self.stop_render_mtx = threading.Lock() + self.bl_viewport = None + self.xpu_slow_mode = False self._start_prman_begin() # hold onto this or python will unload it - self.preload_xpu = preload_xpu() + self.preloaded_dsos = list() + + preload_dsos(self) @classmethod def get_rman_render(self): @@ -313,7 +464,6 @@ def bl_engine(self, bl_engine): def _start_prman_begin(self): argv = [] argv.append("prman") - #argv.append("-Progress") argv.append("-dspyserver") argv.append("%s" % envconfig().rman_it_path) @@ -392,6 +542,7 @@ def _call_brickmake_for_selected(self): args.append(ptc_file) args.append(bkm_file) subprocess.run(args) + string_utils.update_frame_token(self.bl_scene.frame_current) def _check_prman_license(self): if not envconfig().is_valid_license: @@ -423,6 +574,9 @@ def is_regular_rendering(self): # return if we are doing a regular render and not interactive return (self.rman_running and not self.rman_interactive_running) + def is_ipr_to_it(self): + return (self.rman_interactive_running and self.rman_scene.ipr_render_into == 'it') + def do_draw_buckets(self): return get_pref('rman_viewport_draw_bucket', default=True) and self.rman_is_refining @@ -442,6 +596,11 @@ def start_stats_thread(self): __RMAN_STATS_THREAD__.join() __RMAN_STATS_THREAD__ = None __RMAN_STATS_THREAD__ = threading.Thread(target=call_stats_update_payloads, args=(self, )) + if self.rman_is_xpu: + # FIXME: for now, add a 1 second delay before starting the stats thread + # for some reason, XPU doesn't seem to reset the progress between renders + time.sleep(1.0) + self.stats_mgr.reset() __RMAN_STATS_THREAD__.start() def reset(self): @@ -449,6 +608,8 @@ def reset(self): self.rman_license_failed_message = '' self.rman_is_xpu = False self.rman_is_refining = False + self.bl_viewport = None + self.xpu_slow_mode = False def start_render(self, depsgraph, for_background=False): @@ -462,9 +623,14 @@ def start_render(self, depsgraph, for_background=False): if not self._check_prman_license(): return False + do_persistent_data = rm.do_persistent_data + use_compositor = scene_utils.should_use_bl_compositor(self.bl_scene) if for_background: self.rman_render_into = '' is_external = True + if use_compositor: + self.rman_render_into = 'blender' + is_external = False self.rman_callbacks.clear() ec = rman.EventCallbacks.Get() ec.RegisterCallback("Render", render_cb, self) @@ -472,7 +638,7 @@ def start_render(self, depsgraph, for_background=False): if envconfig().getenv('RFB_BATCH_NO_PROGRESS') is None: ec.RegisterCallback("Progress", batch_progress_cb, self) self.rman_callbacks["Progress"] = batch_progress_cb - rman.Dspy.DisableDspyServer() + rman.Dspy.DisableDspyServer() else: self.rman_render_into = rm.render_into @@ -498,27 +664,37 @@ def start_render(self, depsgraph, for_background=False): scene_utils.set_render_variant_config(self.bl_scene, config, render_config) self.rman_is_xpu = (rendervariant == 'xpu') - self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) + boot_strapping = False + bl_rr_helper = None + if self.sg_scene is None: + boot_strapping = True + self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) was_connected = self.stats_mgr.is_connected() - if self.rman_is_xpu and self.rman_render_into == 'blender': - if not was_connected: - # force the stats to start in the case of XPU - # this is so that we can get a progress percentage - # if we can't get it to start, abort - self.stats_mgr.attach() - time.sleep(0.5) # give it a second to attach - if not self.stats_mgr.is_connected(): - self.bl_engine.report({'ERROR'}, 'Cannot start live stats. Aborting XPU render') - self.stop_render(stop_draw_thread=False) - self.del_bl_engine() - return False + if self.rman_is_xpu and not was_connected: + # force the stats to start in the case of XPU + # this is so that we can get a progress percentage + # if we can't get it to start, abort + self.stats_mgr.attach(force=True) + time.sleep(0.5) # give it a second to attach + if not self.stats_mgr.is_connected(): + self.bl_engine.report({'ERROR'}, 'Cannot start live stats. Aborting XPU render') + self.stop_render(stop_draw_thread=False) + self.del_bl_engine() + return False + # Export the scene try: bl_layer = depsgraph.view_layer self.rman_is_exporting = True self.rman_running = True self.start_export_stats_thread() - self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_layer, is_external=is_external) + if boot_strapping: + # This is our first time exporting + self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_layer, is_external=is_external) + else: + # Scene still exists, which means we're in persistent data mode + # Try to get the scene diffs. + self.rman_scene_sync.batch_update_scene(bpy.context, depsgraph) self.rman_is_exporting = False self.stats_mgr.reset_progress() @@ -532,118 +708,40 @@ def start_render(self, depsgraph, for_background=False): self.del_bl_engine() return False + # Start the render render_cmd = "prman" - if self.rman_render_into == 'blender': + if self.rman_render_into == 'blender' or do_persistent_data: render_cmd = "prman -live" render_cmd = self._append_render_cmd(render_cmd) - self.sg_scene.Render(render_cmd) + if boot_strapping: + self.sg_scene.Render(render_cmd) if self.rman_render_into == 'blender': - dspy_dict = display_utils.get_dspy_dict(self.rman_scene) - - render = self.rman_scene.bl_scene.render - render_view = self.bl_engine.active_view_get() - image_scale = render.resolution_percentage / 100.0 - width = int(render.resolution_x * image_scale) - height = int(render.resolution_y * image_scale) - - bl_image_rps= dict() - - # register any AOV's as passes - for i, dspy_nm in enumerate(dspy_dict['displays'].keys()): - if i == 0: - continue - - num_channels = -1 - while num_channels == -1: - num_channels = self.get_numchannels(i) - - dspy = dspy_dict['displays'][dspy_nm] - dspy_chan = dspy['params']['displayChannels'][0] - chan_info = dspy_dict['channels'][dspy_chan] - chan_type = chan_info['channelType']['value'] - - if num_channels == 4: - self.bl_engine.add_pass(dspy_nm, 4, 'RGBA') - elif num_channels == 3: - if chan_type == 'color': - self.bl_engine.add_pass(dspy_nm, 3, 'RGB') - else: - self.bl_engine.add_pass(dspy_nm, 3, 'XYZ') - elif num_channels == 2: - self.bl_engine.add_pass(dspy_nm, 2, 'XY') - else: - self.bl_engine.add_pass(dspy_nm, 1, 'X') - - size_x = width - size_y = height - if render.use_border: - size_x = int(width * (render.border_max_x - render.border_min_x)) - size_y = int(height * (render.border_max_y - render.border_min_y)) - - result = self.bl_engine.begin_result(0, 0, - size_x, - size_y, - view=render_view) - - for i, dspy_nm in enumerate(dspy_dict['displays'].keys()): - if i == 0: - render_pass = result.layers[0].passes.find_by_name("Combined", render_view) - else: - render_pass = result.layers[0].passes.find_by_name(dspy_nm, render_view) - bl_image_rps[i] = render_pass - - if self.rman_is_xpu: - # FIXME: for now, add a 1 second delay before starting the stats thread - # for some reason, XPU doesn't seem to reset the progress between renders - time.sleep(1.0) - self.start_stats_thread() - while self.bl_engine and not self.bl_engine.test_break() and self.rman_is_live_rendering: - time.sleep(0.01) - for i, rp in bl_image_rps.items(): - buffer = self._get_buffer(width, height, image_num=i, - num_channels=rp.channels, - as_flat=False, - back_fill=False, - render=render) - if buffer: - rp.rect = buffer - - if self.bl_engine: - self.bl_engine.update_result(result) - - if result: - if self.bl_engine: - self.bl_engine.end_result(result) - - # Try to save out the displays out to disk. This matches - # Cycles behavior - for i, dspy_nm in enumerate(dspy_dict['displays'].keys()): - filepath = dspy_dict['displays'][dspy_nm]['filePath'] - buffer = self._get_buffer(width, height, image_num=i, as_flat=True) - if buffer: - bl_image = bpy.data.images.new(dspy_nm, width, height) - try: - bl_image.use_generated_float = True - bl_image.filepath_raw = filepath - bl_image.pixels = buffer - bl_image.file_format = 'OPEN_EXR' - bl_image.update() - bl_image.save() - except: - pass - finally: - bpy.data.images.remove(bl_image) - - if not was_connected and self.stats_mgr.is_connected(): - # if stats were not started before rendering, disconnect - self.stats_mgr.disconnect() - else: - self.start_stats_thread() - while self.bl_engine and not self.bl_engine.test_break() and self.rman_is_live_rendering: - time.sleep(0.01) + dspy_dict = display_utils.get_dspy_dict(self.rman_scene, include_holdouts=False) + bl_rr_helper = BlRenderResultHelper(self, self.bl_scene, dspy_dict) + if for_background: + bl_rr_helper.write_aovs = (use_compositor and rm.use_bl_compositor_write_aovs) + else: + bl_rr_helper.write_aovs = True + bl_rr_helper.register_passes() + + self.start_stats_thread() + while self.bl_engine and not self.bl_engine.test_break() and self.rman_is_live_rendering: + time.sleep(0.01) + if bl_rr_helper: + bl_rr_helper.update_passes() + if bl_rr_helper: + bl_rr_helper.finish_passes() + + if not was_connected and self.stats_mgr.is_connected(): + # if stats was not started before rendering, disconnect + self.stats_mgr.disconnect() self.del_bl_engine() - self.stop_render() + if not do_persistent_data: + # If we're not in persistent data mode, stop the renderer immediately + # If we are in persistent mode, we rely on the render_complete and + # render_cancel handlers to stop the renderer (see rman_handlers/__init__.py) + self.stop_render() return True @@ -666,30 +764,76 @@ def start_external_render(self, depsgraph): if rm.external_animation: original_frame = bl_scene.frame_current - rfb_log().debug("Writing to RIB...") - for frame in range(bl_scene.frame_start, bl_scene.frame_end + 1, bl_scene.frame_step): - bl_view_layer = depsgraph.view_layer - config = rman.Types.RtParamList() - render_config = rman.Types.RtParamList() - - self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) - try: - self.bl_engine.frame_set(frame, subframe=0.0) - self.rman_is_exporting = True - self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_view_layer, is_external=True) - self.rman_is_exporting = False - rib_output = string_utils.expand_string(rm.path_rib_output, - frame=frame, - asFilePath=True) - self.sg_scene.Render("rib %s %s" % (rib_output, rib_options)) - self.sgmngr.DeleteScene(self.sg_scene) - except Exception as e: - self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) - rfb_log().error('Export Failed:\n%s' % traceback.format_exc()) - self.stop_render(stop_draw_thread=False) - self.del_bl_engine() - return False + do_persistent_data = rm.do_persistent_data + rfb_log().debug("Writing to RIB...") + time_start = time.time() + if do_persistent_data: + + for frame in range(bl_scene.frame_start, bl_scene.frame_end + 1, bl_scene.frame_step): + bl_view_layer = depsgraph.view_layer + config = rman.Types.RtParamList() + render_config = rman.Types.RtParamList() + + if self.sg_scene is None: + self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) + try: + self.bl_engine.frame_set(frame, subframe=0.0) + rfb_log().debug("Frame: %d" % frame) + if frame == bl_scene.frame_start: + self.rman_is_exporting = True + self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_view_layer, is_external=True) + self.rman_is_exporting = False + else: + self.rman_scene_sync.batch_update_scene(bpy.context, depsgraph) + + rib_output = string_utils.expand_string(rm.path_rib_output, + asFilePath=True) + self.sg_scene.Render("rib %s %s" % (rib_output, rib_options)) + + except Exception as e: + self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) + rfb_log().error('Export Failed:\n%s' % traceback.format_exc()) + self.stop_render(stop_draw_thread=False) + self.del_bl_engine() + return False + + self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() + else: + for frame in range(bl_scene.frame_start, bl_scene.frame_end + 1): + bl_view_layer = depsgraph.view_layer + config = rman.Types.RtParamList() + render_config = rman.Types.RtParamList() + + self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) + try: + self.bl_engine.frame_set(frame, subframe=0.0) + rfb_log().debug("Frame: %d" % frame) + self.rman_is_exporting = True + self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_view_layer, is_external=True) + self.rman_is_exporting = False + + rib_output = string_utils.expand_string(rm.path_rib_output, + asFilePath=True) + self.sg_scene.Render("rib %s %s" % (rib_output, rib_options)) + self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() + + except Exception as e: + self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) + rfb_log().error('Export Failed:\n%s' % traceback.format_exc()) + self.stop_render(stop_draw_thread=False) + self.del_bl_engine() + return False + + if self.sg_scene: + self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() + rfb_log().info("Finished parsing scene. Total time: %s" % string_utils._format_time_(time.time() - time_start)) self.bl_engine.frame_set(original_frame, subframe=0.0) @@ -707,7 +851,6 @@ def start_external_render(self, depsgraph): self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_view_layer, is_external=True) self.rman_is_exporting = False rib_output = string_utils.expand_string(rm.path_rib_output, - frame=bl_scene.frame_current, asFilePath=True) rfb_log().debug("Writing to RIB: %s..." % rib_output) @@ -715,7 +858,9 @@ def start_external_render(self, depsgraph): self.sg_scene.Render("rib %s %s" % (rib_output, rib_options)) rfb_log().debug("Finished writing RIB. Time: %s" % string_utils._format_time_(time.time() - rib_time_start)) rfb_log().info("Finished parsing scene. Total time: %s" % string_utils._format_time_(time.time() - time_start)) - self.sgmngr.DeleteScene(self.sg_scene) + self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() except Exception as e: self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) rfb_log().error('Export Failed:\n%s' % traceback.format_exc()) @@ -727,7 +872,6 @@ def start_external_render(self, depsgraph): spooler = rman_spool.RmanSpool(self, self.rman_scene, depsgraph) spooler.batch_render() self.rman_running = False - self.sg_scene = None self.del_bl_engine() return True @@ -821,7 +965,6 @@ def start_external_bake_render(self, depsgraph): self.rman_scene.export_for_bake_render(depsgraph, self.sg_scene, bl_view_layer, is_external=True) self.rman_is_exporting = False rib_output = string_utils.expand_string(rm.path_rib_output, - frame=frame, asFilePath=True) self.sg_scene.Render("rib %s %s" % (rib_output, rib_options)) except Exception as e: @@ -829,7 +972,9 @@ def start_external_bake_render(self, depsgraph): self.stop_render(stop_draw_thread=False) self.del_bl_engine() return False - self.sgmngr.DeleteScene(self.sg_scene) + self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() self.bl_engine.frame_set(original_frame, subframe=0.0) @@ -849,7 +994,6 @@ def start_external_bake_render(self, depsgraph): self.rman_scene.export_for_bake_render(depsgraph, self.sg_scene, bl_view_layer, is_external=True) self.rman_is_exporting = False rib_output = string_utils.expand_string(rm.path_rib_output, - frame=bl_scene.frame_current, asFilePath=True) rfb_log().debug("Writing to RIB: %s..." % rib_output) @@ -865,12 +1009,13 @@ def start_external_bake_render(self, depsgraph): return False self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() if rm.queuing_system != 'none': spooler = rman_spool.RmanSpool(self, self.rman_scene, depsgraph) spooler.batch_render() self.rman_running = False - self.sg_scene = None self.del_bl_engine() return True @@ -878,14 +1023,17 @@ def start_interactive_render(self, context, depsgraph): global __DRAW_THREAD__ self.reset() - self.rman_interactive_running = True - self.rman_running = True __update_areas__() + if not self._check_prman_license(): + return False + self.rman_interactive_running = True + self.rman_running = True self.bl_scene = depsgraph.scene_eval rm = depsgraph.scene_eval.renderman self.it_port = start_cmd_server() render_into_org = '' - self.rman_render_into = rm.render_ipr_into + self.rman_render_into = self.rman_scene.ipr_render_into + self.bl_viewport = context.space_data self.rman_callbacks.clear() # register the blender display driver @@ -902,6 +1050,7 @@ def start_interactive_render(self, context, depsgraph): self._draw_viewport_buckets = True else: rman.Dspy.EnableDspyServer() + add_ipr_to_it_handlers() except: # force rendering to 'it' rfb_log().error('Could not register Blender display driver. Rendering to "it".') @@ -919,6 +1068,8 @@ def start_interactive_render(self, context, depsgraph): rendervariant = scene_utils.get_render_variant(self.bl_scene) scene_utils.set_render_variant_config(self.bl_scene, config, render_config) self.rman_is_xpu = (rendervariant == 'xpu') + if self.rman_is_xpu: + self.xpu_slow_mode = envconfig().getenv('RFB_XPU_SLOW_MODE', default=False) self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) @@ -943,9 +1094,10 @@ def start_interactive_render(self, context, depsgraph): if render_into_org != '': rm.render_ipr_into = render_into_org - if not self.rman_is_xpu: - # for now, we only set the redraw callback for RIS + if not self.xpu_slow_mode: self.set_redraw_func() + else: + rfb_log().debug("XPU slow mode enabled.") # start a thread to periodically call engine.tag_redraw() __DRAW_THREAD__ = threading.Thread(target=draw_threading_func, args=(self, )) __DRAW_THREAD__.start() @@ -999,18 +1151,14 @@ def start_swatch_render(self, depsgraph): view=render_view) layer = result.layers[0].passes.find_by_name("Combined", render_view) while not self.bl_engine.test_break() and self.rman_is_live_rendering: - time.sleep(0.001) + time.sleep(0.01) if layer: - buffer = self._get_buffer(width, height, image_num=0, as_flat=False) - if buffer: + buffer = self._get_buffer(width, height, image_num=0, num_channels=4, as_flat=False) + if buffer is None: + break + else: layer.rect = buffer self.bl_engine.update_result(result) - # try to get the buffer one last time before exiting - if layer: - buffer = self._get_buffer(width, height, image_num=0, as_flat=False) - if buffer: - layer.rect = buffer - self.bl_engine.update_result(result) self.stop_render() self.bl_engine.end_result(result) self.del_bl_engine() @@ -1035,15 +1183,18 @@ def start_export_rib_selected(self, context, rib_path, export_materials=True, ex self.rman_scene.export_for_rib_selection(context, self.sg_scene) self.rman_is_exporting = False rib_output = string_utils.expand_string(rib_path, - frame=frame, asFilePath=True) - self.sg_scene.Render("rib " + rib_output + " -archive") + cmd = 'rib ' + rib_output + ' -archive' + cmd = cmd + ' -bbox ' + transform_utils.get_world_bounding_box(context.selected_objects) + self.sg_scene.Render(cmd) except Exception as e: self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) self.stop_render(stop_draw_thread=False) self.del_bl_engine() return False - self.sgmngr.DeleteScene(self.sg_scene) + self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() bl_scene.frame_set(original_frame, subframe=0.0) else: config = rman.Types.RtParamList() @@ -1055,9 +1206,10 @@ def start_export_rib_selected(self, context, rib_path, export_materials=True, ex self.rman_scene.export_for_rib_selection(context, self.sg_scene) self.rman_is_exporting = False rib_output = string_utils.expand_string(rib_path, - frame=bl_scene.frame_current, asFilePath=True) - self.sg_scene.Render("rib " + rib_output + " -archive") + cmd = 'rib ' + rib_output + ' -archive' + cmd = cmd + ' -bbox ' + transform_utils.get_world_bounding_box(context.selected_objects) + self.sg_scene.Render(cmd) except Exception as e: self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) rfb_log().error('Export Failed:\n%s' % traceback.format_exc()) @@ -1065,9 +1217,9 @@ def start_export_rib_selected(self, context, rib_path, export_materials=True, ex self.del_bl_engine() return False self.sgmngr.DeleteScene(self.sg_scene) + self.sg_scene = None + self.rman_scene.reset() - - self.sg_scene = None self.rman_running = False return True @@ -1097,6 +1249,7 @@ def stop_render(self, stop_draw_thread=True): for k,v in self.rman_callbacks.items(): ec.UnregisterCallback(k, v, self) self.rman_callbacks.clear() + remove_ipr_to_it_handlers() self.rman_is_live_rendering = False @@ -1156,78 +1309,79 @@ def reset_buffer_updated(self): def draw_pixels(self, width, height): self.viewport_res_x = width self.viewport_res_y = height - if self.rman_is_viewport_rendering: - dspy_plugin = self.get_blender_dspy_plugin() - - # (the driver will handle pixel scaling to the given viewport size) - dspy_plugin.DrawBufferToBlender(ctypes.c_int(width), ctypes.c_int(height)) - - if self.do_draw_buckets(): - # draw bucket indicator - image_num = 0 - arXMin = ctypes.c_int(0) - arXMax = ctypes.c_int(0) - arYMin = ctypes.c_int(0) - arYMax = ctypes.c_int(0) - dspy_plugin.GetActiveRegion(ctypes.c_size_t(image_num), ctypes.byref(arXMin), ctypes.byref(arXMax), ctypes.byref(arYMin), ctypes.byref(arYMax)) - if ( (arXMin.value + arXMax.value + arYMin.value + arYMax.value) > 0): - yMin = height-1 - arYMin.value - yMax = height-1 - arYMax.value - xMin = arXMin.value - xMax = arXMax.value - if self.rman_scene.viewport_render_res_mult != 1.0: - # render resolution multiplier is set, we need to re-scale the bucket markers - scaled_width = width * self.rman_scene.viewport_render_res_mult - xMin = int(width * ((arXMin.value) / (scaled_width))) - xMax = int(width * ((arXMax.value) / (scaled_width))) - - scaled_height = height * self.rman_scene.viewport_render_res_mult - yMin = height-1 - int(height * ((arYMin.value) / (scaled_height))) - yMax = height-1 - int(height * ((arYMax.value) / (scaled_height))) - - vertices = [] - c1 = (xMin, yMin) - c2 = (xMax, yMin) - c3 = (xMax, yMax) - c4 = (xMin, yMax) - vertices.append(c1) - vertices.append(c2) - vertices.append(c3) - vertices.append(c4) - indices = [(0, 1), (1, 2), (2,3), (3, 0)] - - # we've reach our max buckets, pop the oldest one off the list - if len(self.viewport_buckets) > RFB_VIEWPORT_MAX_BUCKETS: - self.viewport_buckets.pop() - self.viewport_buckets.insert(0,[vertices, indices]) - - bucket_color = get_pref('rman_viewport_bucket_color', default=RMAN_RENDERMAN_BLUE) - - # draw from newest to oldest - for v, i in (self.viewport_buckets): - shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') - shader.uniform_float("color", bucket_color) - batch = batch_for_shader(shader, 'LINES', {"pos": v}, indices=i) - shader.bind() - batch.draw(shader) - - # draw progress bar at the bottom of the viewport - if self.do_draw_progressbar(): - progress = self.stats_mgr._progress / 100.0 - progress_color = get_pref('rman_viewport_progress_color', default=RMAN_RENDERMAN_BLUE) - shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') - shader.uniform_float("color", progress_color) - vtx = [(0, 1), (width * progress, 1)] - batch = batch_for_shader(shader, 'LINES', {"pos": vtx}) - shader.bind() - batch.draw(shader) + if self.rman_is_viewport_rendering is False: + return + + dspy_plugin = self.get_blender_dspy_plugin() + + # (the driver will handle pixel scaling to the given viewport size) + dspy_plugin.DrawBufferToBlender(ctypes.c_int(width), ctypes.c_int(height)) + if self.do_draw_buckets(): + # draw bucket indicator + image_num = 0 + arXMin = ctypes.c_int(0) + arXMax = ctypes.c_int(0) + arYMin = ctypes.c_int(0) + arYMax = ctypes.c_int(0) + dspy_plugin.GetActiveRegion(ctypes.c_size_t(image_num), ctypes.byref(arXMin), ctypes.byref(arXMax), ctypes.byref(arYMin), ctypes.byref(arYMax)) + if ( (arXMin.value + arXMax.value + arYMin.value + arYMax.value) > 0): + yMin = height-1 - arYMin.value + yMax = height-1 - arYMax.value + xMin = arXMin.value + xMax = arXMax.value + if self.rman_scene.viewport_render_res_mult != 1.0: + # render resolution multiplier is set, we need to re-scale the bucket markers + scaled_width = width * self.rman_scene.viewport_render_res_mult + xMin = int(width * ((arXMin.value) / (scaled_width))) + xMax = int(width * ((arXMax.value) / (scaled_width))) + + scaled_height = height * self.rman_scene.viewport_render_res_mult + yMin = height-1 - int(height * ((arYMin.value) / (scaled_height))) + yMax = height-1 - int(height * ((arYMax.value) / (scaled_height))) + + vertices = [] + c1 = (xMin, yMin) + c2 = (xMax, yMin) + c3 = (xMax, yMax) + c4 = (xMin, yMax) + vertices.append(c1) + vertices.append(c2) + vertices.append(c3) + vertices.append(c4) + indices = [(0, 1), (1, 2), (2,3), (3, 0)] + + # we've reach our max buckets, pop the oldest one off the list + if len(self.viewport_buckets) > RFB_VIEWPORT_MAX_BUCKETS: + self.viewport_buckets.pop() + self.viewport_buckets.insert(0,[vertices, indices]) + + bucket_color = get_pref('rman_viewport_bucket_color', default=RMAN_RENDERMAN_BLUE) + + # draw from newest to oldest + shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') + shader.bind() + shader.uniform_float("color", bucket_color) + for v, i in (self.viewport_buckets): + batch = batch_for_shader(shader, 'LINES', {"pos": v}, indices=i) + batch.draw(shader) + + # draw progress bar at the bottom of the viewport + if self.do_draw_progressbar(): + progress = self.stats_mgr._progress / 100.0 + shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') + shader.bind() + progress_color = get_pref('rman_viewport_progress_color', default=RMAN_RENDERMAN_BLUE) + shader.uniform_float("color", progress_color) + vtx = [(0, 1), (width * progress, 1)] + batch = batch_for_shader(shader, 'LINES', {"pos": vtx}) + batch.draw(shader) def get_numchannels(self, image_num): dspy_plugin = self.get_blender_dspy_plugin() num_channels = dspy_plugin.GetNumberOfChannels(ctypes.c_size_t(image_num)) return num_channels - def _get_buffer(self, width, height, image_num=0, num_channels=-1, back_fill=True, as_flat=True, render=None): + def _get_buffer(self, width, height, image_num=0, num_channels=-1, raw_buffer=False, back_fill=True, as_flat=True, render=None): dspy_plugin = self.get_blender_dspy_plugin() if num_channels == -1: num_channels = self.get_numchannels(image_num) @@ -1240,37 +1394,34 @@ def _get_buffer(self, width, height, image_num=0, num_channels=-1, back_fill=Tru f.restype = ctypes.POINTER(ArrayType) try: - buffer = numpy.array(f(ctypes.c_size_t(image_num)).contents) - pixels = list() + buffer = numpy.array(f(ctypes.c_size_t(image_num)).contents, dtype=numpy.float32) - # we need to flip the image - # also, Blender is expecting a 4 channel image + if raw_buffer: + if not as_flat: + buffer.shape = (height, width, num_channels) + return buffer if as_flat: - if num_channels == 4: - return buffer.tolist() + if (num_channels == 4) or not back_fill: + return buffer else: + p_pos = 0 + pixels = numpy.ones(width*height*4, dtype=numpy.float32) for y in range(0, height): i = (width * y * num_channels) for x in range(0, width): j = i + (num_channels * x) if num_channels == 3: - pixels.append(buffer[j]) - pixels.append(buffer[j+1]) - pixels.append(buffer[j+2]) - pixels.append(1.0) + pixels[p_pos:p_pos+3] = buffer[j:j+3] elif num_channels == 2: - pixels.append(buffer[j]) - pixels.append(buffer[j+1]) - pixels.append(1.0) - pixels.append(1.0) + pixels[p_pos:p_pos+2] = buffer[j:j+2] elif num_channels == 1: - pixels.append(buffer[j]) - pixels.append(buffer[j]) - pixels.append(buffer[j]) - pixels.append(1.0) - return pixels + pixels[p_pos] = buffer[j] + pixels[p_pos+1] = buffer[j] + pixels[p_pos+2] = buffer[j] + p_pos += 4 + return pixels else: if render and render.use_border: start_x = 0 @@ -1280,50 +1431,51 @@ def _get_buffer(self, width, height, image_num=0, num_channels=-1, back_fill=Tru if render.border_min_y > 0.0: - start_y = int(height * (render.border_min_y))-1 + start_y = round(height * render.border_min_y)-1 if render.border_max_y > 0.0: - end_y = int(height * (render.border_max_y))-1 + end_y = round(height * render.border_max_y)-1 if render.border_min_x > 0.0: - start_x = int(width * render.border_min_x)-1 + start_x = round(width * render.border_min_x)-1 if render.border_max_x < 1.0: - end_x = int(width * render.border_max_x)-2 + end_x = round(width * render.border_max_x)-2 # return the buffer as a list of lists + if back_fill: + pixels = numpy.ones( ((end_x-start_x)*(end_y-start_y), 4), dtype=numpy.float32 ) + else: + pixels = numpy.zeros( ((end_x-start_x)*(end_y-start_y), num_channels) ) + p_pos = 0 for y in range(start_y, end_y): i = (width * y * num_channels) for x in range(start_x, end_x): j = i + (num_channels * x) - if not back_fill: - pixels.append(buffer[j:j+num_channels]) - continue - - if num_channels == 4: - pixels.append(buffer[j:j+4]) - continue - - pixel = [1.0] * num_channels - pixel[0] = buffer[j] - if num_channels == 3: - pixel[1] = buffer[j+1] - pixel[2] = buffer[j+2] - elif num_channels == 2: - pixel[1] = buffer[j+1] - elif num_channels == 1: - pixel[1] = buffer[j] - pixel[2] = buffer[j] - - pixels.append(pixel) + if (num_channels==4) or not back_fill: + # just slice + pixels[p_pos] = buffer[j:j+num_channels] + else: + pixels[p_pos][0] = buffer[j] + if num_channels == 3: + pixels[p_pos][1] = buffer[j+1] + pixels[p_pos][2] = buffer[j+2] + elif num_channels == 2: + pixels[p_pos][1] = buffer[j+1] + elif num_channels == 1: + pixels[p_pos][1] = buffer[j] + pixels[p_pos][2] = buffer[j] + + p_pos += 1 return pixels else: - buffer = numpy.reshape(buffer, (-1, num_channels)) - return buffer.tolist() + buffer.shape = (-1, num_channels) + return buffer except Exception as e: rfb_log().debug("Could not get buffer: %s" % str(e)) return None - def save_viewport_snapshot(self, frame=1): + @time_this + def save_viewport_snapshot(self): if not self.rman_is_viewport_rendering: return @@ -1331,16 +1483,31 @@ def save_viewport_snapshot(self, frame=1): width = int(self.viewport_res_x * res_mult) height = int(self.viewport_res_y * res_mult) - pixels = self._get_buffer(width, height) - if not pixels: - rfb_log().error("Could not save snapshot.") - return - - nm = 'rman_viewport_snapshot__%d' % len(bpy.data.images) - nm = string_utils.expand_string(nm, frame=frame) - img = bpy.data.images.new(nm, width, height, float_buffer=True, alpha=True) - img.pixels = pixels - img.update() + nm = 'rman_viewport_snapshot__%d.exr' % len(bpy.data.images) + nm = string_utils.expand_string(nm) + if hasattr(ice, 'FromArray'): + buffer = self._get_buffer(width, height, as_flat=False, raw_buffer=True) + if buffer is None: + rfb_log().error("Could not save snapshot.") + return + img = ice.FromArray(buffer) + img = img.Flip(False, True, False) + filepath = os.path.join(bpy.app.tempdir, nm) + img.Save(filepath, ice.constants.FMT_EXRFLOAT) + bpy.ops.image.open('EXEC_DEFAULT', filepath=filepath) + bpy.data.images[-1].pack() + os.remove(filepath) + else: + buffer = self._get_buffer(width, height) + if buffer is None: + rfb_log().error("Could not save snapshot.") + return + + img = bpy.data.images.new(nm, width, height, float_buffer=True, alpha=True) + if isinstance(buffer, numpy.ndarray): + buffer = buffer.tolist() + img.pixels.foreach_set(buffer) + img.update() def update_scene(self, context, depsgraph): if self.rman_interactive_running: diff --git a/rman_scene.py b/rman_scene.py index 6cb3cd7f..3bdcf335 100644 --- a/rman_scene.py +++ b/rman_scene.py @@ -23,6 +23,7 @@ from .rman_translators.rman_emitter_translator import RmanEmitterTranslator from .rman_translators.rman_empty_translator import RmanEmptyTranslator from .rman_translators.rman_alembic_translator import RmanAlembicTranslator +from .rman_translators.rman_hair_curves_translator import RmanHairCurvesTranslator # utils from .rfb_utils import object_utils @@ -52,8 +53,8 @@ class RmanScene(object): ''' - The RmanScene handles translating the Blender scene. - + The RmanScene handles translating the Blender scene. + Attributes: rman_render (RmanRender) - pointer back to the current RmanRender object rman () - rman python module @@ -63,7 +64,7 @@ class RmanScene(object): bl_scene (bpy.types.Scene) - the current Blender scene object bl_frame_current (int) - the current Blender frame bl_view_layer (bpy.types.ViewLayer) - the current Blender view layer - rm_rl (RendermanRenderLayerSettings) - the current rman layer + rm_rl (RendermanRenderLayerSettings) - the current rman layer do_motion_blur (bool) - user requested for motion blur rman_bake (bool) - user requested a bake render is_interactive (bool) - whether we are in interactive mode @@ -71,16 +72,14 @@ class RmanScene(object): is_viewport_render (bool) - whether we are rendering into Blender's viewport scene_solo_light (bool) - user has solo'd a light (all other lights are muted) rman_materials (dict) - dictionary of scene's materials - rman_objects (dict) - dictionary of all objects rman_translators (dict) - dictionary of all RmanTranslator(s) rman_particles (dict) - dictionary of all particle systems used rman_cameras (dict) - dictionary of all cameras in the scene obj_hash (dict) - dictionary of hashes to objects ( for object picking ) moving_objects (dict) - dictionary of objects that are moving/deforming in the scene - processed_obs (dict) - dictionary of objects already processed - motion_steps (set) - the full set of motion steps for the scene, including + motion_steps (set) - the full set of motion steps for the scene, including overrides from individual objects - main_camera (RmanSgCamera) - pointer to the main scene camera + main_camera (RmanSgCamera) - pointer to the main scene camera rman_root_sg_node (RixSGGroup) - the main root RixSceneGraph node render_default_light (bool) - whether to add a "headlight" light when there are no lights in the scene world_df_node (RixSGShader) - a display filter shader that represents the world color @@ -103,7 +102,7 @@ def __init__(self, rman_render=None): self.bl_scene = None self.bl_frame_current = None self.bl_view_layer = None - self.rm_rl = None + self.rm_rl = None self.do_motion_blur = False self.rman_bake = False @@ -116,13 +115,12 @@ def __init__(self, rman_render=None): self.is_xpu = False self.rman_materials = dict() - self.rman_objects = dict() self.rman_translators = dict() self.rman_particles = dict() self.rman_cameras = dict() - self.obj_hash = dict() + self.obj_hash = dict() self.moving_objects = dict() - self.processed_obs = [] + self.rman_prototypes = dict() self.motion_steps = set() self.main_camera = None @@ -136,9 +134,10 @@ def __init__(self, rman_render=None): self.num_object_instances = 0 self.num_objects_in_viewlayer = 0 self.objects_in_viewlayer = list() - self.bl_local_view = False - self.create_translators() + self.ipr_render_into = 'blender' + + self.create_translators() def create_translators(self): @@ -148,10 +147,11 @@ def create_translators(self): self.rman_translators['CAMERA'] = RmanCameraTranslator(rman_scene=self) self.rman_translators['LIGHT'] = RmanLightTranslator(rman_scene=self) self.rman_translators['LIGHTFILTER'] = RmanLightFilterTranslator(rman_scene=self) - self.rman_translators['MATERIAL'] = RmanMaterialTranslator(rman_scene=self) - self.rman_translators['HAIR'] = RmanHairTranslator(rman_scene=self) + self.rman_translators['MATERIAL'] = RmanMaterialTranslator(rman_scene=self) + self.rman_translators['HAIR'] = RmanHairTranslator(rman_scene=self) self.rman_translators['GROUP'] = RmanGroupTranslator(rman_scene=self) self.rman_translators['EMPTY'] = RmanEmptyTranslator(rman_scene=self) + self.rman_translators['EMPTY_INSTANCER'] = RmanEmptyTranslator(rman_scene=self) self.rman_translators['POINTS'] = RmanPointsTranslator(rman_scene=self) self.rman_translators['META'] = RmanBlobbyTranslator(rman_scene=self) self.rman_translators['PARTICLES'] = RmanParticlesTranslator(rman_scene=self) @@ -169,33 +169,33 @@ def create_translators(self): self.rman_translators['RI_VOLUME'] = RmanVolumeTranslator(rman_scene=self) self.rman_translators['BRICKMAP'] = RmanBrickmapTranslator(rman_scene=self) self.rman_translators['ALEMBIC'] = RmanAlembicTranslator(rman_scene=self) + self.rman_translators['CURVES'] = RmanHairCurvesTranslator(rman_scene=self) def _find_renderman_layer(self): self.rm_rl = None if self.bl_view_layer.renderman.use_renderman: - self.rm_rl = self.bl_view_layer.renderman + self.rm_rl = self.bl_view_layer.renderman def reset(self): # clear out dictionaries etc. self.rman_materials.clear() - self.rman_objects.clear() self.rman_particles.clear() - self.rman_cameras.clear() - self.obj_hash.clear() - self.motion_steps = set() + self.rman_cameras.clear() + self.obj_hash.clear() + self.motion_steps = set() self.moving_objects.clear() - - self.processed_obs.clear() - + self.rman_prototypes.clear() + + self.main_camera = None self.render_default_light = False self.world_df_node = None self.default_light = None - self.is_xpu = False + self.is_xpu = False self.num_object_instances = 0 self.num_objects_in_viewlayer = 0 self.objects_in_viewlayer.clear() - try: + try: if self.is_viewport_render: self.viewport_render_res_mult = float(self.context.scene.renderman.viewport_render_res_mult) else: @@ -214,7 +214,6 @@ def export_for_final_render(self, depsgraph, sg_scene, bl_view_layer, is_externa self.external_render = is_external self.is_interactive = False self.is_viewport_render = False - self.bl_local_view = False self.do_motion_blur = self.bl_scene.renderman.motion_blur self.export() @@ -230,7 +229,6 @@ def export_for_bake_render(self, depsgraph, sg_scene, bl_view_layer, is_external self.is_viewport_render = False self.do_motion_blur = self.bl_scene.renderman.motion_blur self.rman_bake = True - self.bl_local_view = False if self.bl_scene.renderman.hider_type == 'BAKE_BRICKMAP_SELECTED': self.export_bake_brickmap_selected() @@ -241,47 +239,42 @@ def export_for_interactive_render(self, context, depsgraph, sg_scene): self.sg_scene = sg_scene self.context = context self.bl_view_layer = context.view_layer - self.bl_scene = depsgraph.scene_eval + self.bl_scene = depsgraph.scene_eval self._find_renderman_layer() - self.bl_local_view = context.space_data.local_view self.depsgraph = depsgraph self.external_render = False self.is_interactive = True self.is_viewport_render = False self.rman_bake = False - - if self.bl_scene.renderman.render_ipr_into == 'blender': + + if self.ipr_render_into == 'blender': self.is_viewport_render = True self.do_motion_blur = False - self.export() + self.export() def export_for_rib_selection(self, context, sg_scene): self.reset() self.bl_scene = context.scene - self.bl_local_view = False self.bl_frame_current = self.bl_scene.frame_current self.sg_scene = sg_scene self.context = context self.bl_view_layer = context.view_layer self._find_renderman_layer() - self.rman_bake = False + self.rman_bake = False self.external_render = False self.is_interactive = False - self.is_viewport_render = False - + self.is_viewport_render = False + self.depsgraph = context.evaluated_depsgraph_get() self.export_root_sg_node() - objs = context.selected_objects self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) - self.export_data_blocks(objs) - self.export_instances(obj_selected=objs) + self.export_data_blocks(selected_objects=True) def export_for_swatch_render(self, depsgraph, sg_scene): self.sg_scene = sg_scene self.context = bpy.context #None - self.bl_local_view = False self.bl_scene = depsgraph.scene_eval self.depsgraph = depsgraph self.external_render = False @@ -305,28 +298,28 @@ def export(self): string_utils.set_var('layer', self.bl_view_layer.name.replace(' ', '_')) self.bl_frame_current = self.bl_scene.frame_current + string_utils.update_frame_token(self.bl_frame_current) rfb_log().debug("Creating root scene graph node") - self.export_root_sg_node() + self.export_root_sg_node() rfb_log().debug("Calling export_materials()") #self.export_materials(bpy.data.materials) - self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) - + self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) + # tell the texture manager to start converting any unconverted textures - # normally textures are converted as they are added to the scene + # normally textures are converted as they are added to the scene rfb_log().debug("Calling txmake_all()") - texture_utils.get_txmanager().rman_scene = self + texture_utils.get_txmanager().rman_scene = self texture_utils.get_txmanager().txmake_all(blocking=True) self.scene_any_lights = self._scene_has_lights() - + rfb_log().debug("Calling export_data_blocks()") - #self.export_data_blocks(bpy.data.objects) - self.export_data_blocks([x for x in self.depsgraph.ids if isinstance(x, bpy.types.Object)]) + self.export_data_blocks() - self.export_searchpaths() - self.export_global_options() + self.export_searchpaths() + self.export_global_options() self.export_hider() self.export_integrator() @@ -335,7 +328,7 @@ def export(self): # export default light self.export_defaultlight() self.main_camera.sg_node.AddChild(self.default_light) - + self.export_displays() self.export_samplefilters() self.export_displayfilters() @@ -343,20 +336,17 @@ def export(self): if self.do_motion_blur: rfb_log().debug("Calling export_instances_motion()") self.export_instances_motion() - else: - rfb_log().debug("Calling export_instances()") - self.export_instances() self.rman_render.stats_mgr.set_export_stats("Finished Export", 1.0) self.num_object_instances = len(self.depsgraph.object_instances) - self.num_objects_in_viewlayer = len(self.depsgraph.view_layer.objects) - self.objects_in_viewlayer = [o for o in self.depsgraph.view_layer.objects] - self.check_solo_light() + visible_objects = getattr(self.context, 'visible_objects', list()) + self.num_objects_in_viewlayer = len(visible_objects) + self.objects_in_viewlayer = [o for o in visible_objects] if self.is_interactive: self.export_viewport_stats() - else: - self.export_stats() + else: + self.export_stats() def export_bake_render_scene(self): self.reset() @@ -370,32 +360,32 @@ def export_bake_render_scene(self): self.export_root_sg_node() rfb_log().debug("Calling export_materials()") - self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) - + self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) + rfb_log().debug("Calling txmake_all()") - texture_utils.get_txmanager().rman_scene = self + texture_utils.get_txmanager().rman_scene = self texture_utils.get_txmanager().txmake_all(blocking=True) self.scene_any_lights = self._scene_has_lights() - + rm = self.bl_scene.renderman rman_root_sg_node = self.get_root_sg_node() attrs = rman_root_sg_node.GetAttributes() attrs.SetFloat("dice:worlddistancelength", rm.rman_bake_illlum_density) - rman_root_sg_node.SetAttributes(attrs) + rman_root_sg_node.SetAttributes(attrs) rfb_log().debug("Calling export_data_blocks()") - self.export_data_blocks(bpy.data.objects) + self.export_data_blocks() - self.export_searchpaths() - self.export_global_options() + self.export_searchpaths() + self.export_global_options() self.export_hider() self.export_integrator() self.export_cameras([c for c in self.depsgraph.objects if isinstance(c.data, bpy.types.Camera)]) # export default light self.export_defaultlight() - self.main_camera.sg_node.AddChild(self.default_light) + self.main_camera.sg_node.AddChild(self.default_light) self.export_bake_displays() self.export_samplefilters() @@ -404,13 +394,10 @@ def export_bake_render_scene(self): if self.do_motion_blur: rfb_log().debug("Calling export_instances_motion()") self.export_instances_motion() - else: - rfb_log().debug("Calling export_instances()") - self.export_instances() options = self.sg_scene.GetOptions() bake_resolution = int(rm.rman_bake_illlum_res) - options.SetIntegerArray(self.rman.Tokens.Rix.k_Ri_FormatResolution, (bake_resolution, bake_resolution), 2) + options.SetIntegerArray(self.rman.Tokens.Rix.k_Ri_FormatResolution, (bake_resolution, bake_resolution), 2) self.sg_scene.SetOptions(options) def export_bake_brickmap_selected(self): @@ -427,19 +414,19 @@ def export_bake_brickmap_selected(self): rfb_log().debug("Calling export_materials()") self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) rfb_log().debug("Calling txmake_all()") - texture_utils.get_txmanager().rman_scene = self - texture_utils.get_txmanager().txmake_all(blocking=True) + texture_utils.get_txmanager().rman_scene = self + texture_utils.get_txmanager().txmake_all(blocking=True) + + self.scene_any_lights = self._scene_has_lights() - self.scene_any_lights = self._scene_has_lights() - rm = self.bl_scene.renderman rman_root_sg_node = self.get_root_sg_node() attrs = rman_root_sg_node.GetAttributes() attrs.SetFloat("dice:worlddistancelength", rm.rman_bake_illlum_density) - rman_root_sg_node.SetAttributes(attrs) + rman_root_sg_node.SetAttributes(attrs) - self.export_searchpaths() - self.export_global_options() + self.export_searchpaths() + self.export_global_options() self.export_hider() self.export_integrator() self.export_cameras([c for c in self.depsgraph.objects if isinstance(c.data, bpy.types.Camera)]) @@ -450,18 +437,17 @@ def export_bake_brickmap_selected(self): ob = self.context.active_object self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) - objects_needed = [x for x in self.bl_scene.objects if object_utils._detect_primitive_(x) == 'LIGHT'] - objects_needed.append(ob) - self.export_data_blocks(objects_needed) - self.export_instances() + objects_needed = [x.original for x in self.bl_scene.objects if object_utils._detect_primitive_(x) == 'LIGHT'] + objects_needed.append(ob.original) + self.export_data_blocks(objects_list=objects_needed) self.export_samplefilters() self.export_displayfilters() options = self.sg_scene.GetOptions() bake_resolution = int(rm.rman_bake_illlum_res) - options.SetIntegerArray(self.rman.Tokens.Rix.k_Ri_FormatResolution, (bake_resolution, bake_resolution), 2) - self.sg_scene.SetOptions(options) + options.SetIntegerArray(self.rman.Tokens.Rix.k_Ri_FormatResolution, (bake_resolution, bake_resolution), 2) + self.sg_scene.SetOptions(options) # Display display_driver = 'pointcloud' @@ -472,8 +458,8 @@ def export_bake_brickmap_selected(self): render_output = string_utils.expand_string(render_output) display = self.rman.SGManager.RixSGShader("Display", display_driver, render_output) display.params.SetString("mode", 'Ci') - self.main_camera.sg_camera_node.SetDisplay(display) - + self.main_camera.sg_camera_node.SetDisplay(display) + def export_swatch_render_scene(self): self.reset() @@ -493,11 +479,11 @@ def export_swatch_render_scene(self): self.sg_scene.SetOptions(options) # searchpaths - self.export_searchpaths() + self.export_searchpaths() - # integrator - integrator_sg = self.rman.SGManager.RixSGShader("Integrator", "PxrDirectLighting", "integrator") - self.sg_scene.SetIntegrator(integrator_sg) + # integrator + integrator_sg = self.rman.SGManager.RixSGShader("Integrator", "PxrPathTracer", "integrator") + self.sg_scene.SetIntegrator(integrator_sg) # camera self.export_cameras([c for c in self.depsgraph.objects if isinstance(c.data, bpy.types.Camera)]) @@ -510,41 +496,24 @@ def export_swatch_render_scene(self): self.sg_scene.SetDisplayChannel([dspy_chan_Ci, dspy_chan_a]) display = self.rman.SGManager.RixSGShader("Display", display_driver, 'blender_preview') display.params.SetString("mode", 'Ci,a') - self.main_camera.sg_camera_node.SetDisplay(display) + self.main_camera.sg_camera_node.SetDisplay(display) rfb_log().debug("Calling materials()") self.export_materials([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Material)]) rfb_log().debug("Calling export_data_blocks()") - - self.export_data_blocks([m for m in self.depsgraph.ids if isinstance(m, bpy.types.Object)]) - self.export_instances() - def export_root_sg_node(self): - + self.export_data_blocks() + + def set_root_lightlinks(self, rixattrs=None): rm = self.bl_scene.renderman root_sg = self.get_root_sg_node() - attrs = root_sg.GetAttributes() - - # set any properties marked riattr in the config file - for prop_name, meta in rm.prop_meta.items(): - if 'riattr' not in meta: - continue - - val = getattr(rm, prop_name) - ri_name = meta['riattr'] - is_array = False - array_len = -1 - if 'arraySize' in meta: - is_array = True - array_len = meta['arraySize'] - if type(val) == str and val.startswith('['): - val = eval(val) - param_type = meta['renderman_type'] - property_utils.set_rix_param(attrs, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=rm) + attrs = rixattrs + if rixattrs is None: + attrs = root_sg.GetAttributes() + all_lightfilters = [string_utils.sanitize_node_name(l.name) for l in scene_utils.get_all_lightfilters(self.bl_scene)] if rm.invert_light_linking: all_lights = [string_utils.sanitize_node_name(l.name) for l in scene_utils.get_all_lights(self.bl_scene, include_light_filters=False)] - all_lightfilters = [string_utils.sanitize_node_name(l.name) for l in scene_utils.get_all_lightfilters(self.bl_scene)] for ll in rm.light_links: light_ob = ll.light_ob light_nm = string_utils.sanitize_node_name(light_ob.name) @@ -554,7 +523,7 @@ def export_root_sg_node(self): all_lights.remove(light_nm) elif light_nm in all_lightfilters: all_lightfilters.remove(light_nm) - + if all_lights: attrs.SetString(self.rman.Tokens.Rix.k_lighting_subset, ' '. join(all_lights) ) else: @@ -563,403 +532,318 @@ def export_root_sg_node(self): if all_lightfilters: attrs.SetString(self.rman.Tokens.Rix.k_lightfilter_subset, ' '. join(all_lightfilters) ) else: - attrs.SetString(self.rman.Tokens.Rix.k_lightfilter_subset, '*') - + attrs.SetString(self.rman.Tokens.Rix.k_lightfilter_subset, '*') + else: + attrs.SetString(self.rman.Tokens.Rix.k_lightfilter_subset, ','. join(all_lightfilters) ) + + if rixattrs is None: + root_sg.SetAttributes(attrs) + + def export_root_sg_node(self): + + rm = self.bl_scene.renderman + root_sg = self.get_root_sg_node() + attrs = root_sg.GetAttributes() + + # set any properties marked riattr in the config file + for prop_name, meta in rm.prop_meta.items(): + property_utils.set_riattr_bl_prop(attrs, prop_name, meta, rm, check_inherit=False, remove=False) + + self.set_root_lightlinks(rixattrs=attrs) root_sg.SetAttributes(attrs) - + def get_root_sg_node(self): return self.sg_scene.Root() def export_materials(self, materials): - for mat in materials: + for mat in materials: db_name = object_utils.get_db_name(mat) rman_sg_material = self.rman_translators['MATERIAL'].export(mat.original, db_name) - if rman_sg_material: + if rman_sg_material: self.rman_materials[mat.original] = rman_sg_material - - def export_data_blocks(self, data_blocks): - total = len(data_blocks) - for i, obj in enumerate(data_blocks): - if obj.type not in ('ARMATURE', 'CAMERA'): - ob = obj.evaluated_get(self.depsgraph) - self.export_data_block(ob) - rfb_log().debug(" Exported %d/%d data blocks... (%s)" % (i, total, obj.name)) - self.rman_render.stats_mgr.set_export_stats("Exporting data blocks",i/total) - - def export_data_block(self, db_ob): - - # FIXME? - # We currently export a unique geometry/mesh per Object - # This means we're not actually sharing datablocks per Object, even if they are shared - # in Blender. We do this for a couple of reasons: - # - # 1. Each object can have different modifiers applied. This includes applying a subdiv and/or bevel modifiers. - # 2. Each object may want a different number of deformation motion samples - # - # This is incredibly wasteful when these don't apply. We could try and detect this case and - # create a shareable geometry. - - obj = db_ob - - if obj and obj.type not in ('ARMATURE', 'CAMERA'): - ob = obj.evaluated_get(self.depsgraph) - rman_type = object_utils._detect_primitive_(ob) - db_name = object_utils.get_db_name(ob, rman_type=rman_type) - if rman_type == 'LIGHT': - if ob.data.renderman.renderman_light_role == 'RMAN_LIGHTFILTER': - # skip if this is a light filter - # these will be exported when we do regular lights - return - translator = self.rman_translators.get(rman_type, None) - if not translator: - return - - rman_sg_node = None - if ob.original in self.rman_objects: - return + def check_visibility(self, instance): + if not self.is_interactive: + return True + viewport = self.context.space_data + if viewport is None or viewport.type != 'VIEW_3D': + return True + + if instance.is_instance: + ob_eval = instance.instance_object + ob_eval_visible = ob_eval.visible_in_viewport_get(viewport) + parent_visible = instance.parent.visible_in_viewport_get(viewport) + return (ob_eval_visible or parent_visible) + + ob_eval = instance.object.evaluated_get(self.depsgraph) + visible = ob_eval.visible_in_viewport_get(viewport) + return visible + + def is_instance_selected(self, instance): + ob = instance.object + parent = None + if instance.is_instance: + parent = instance.parent - rman_sg_node = translator.export(ob, db_name) - if not rman_sg_node: - return - rman_sg_node.rman_type = rman_type - self.rman_objects[ob.original] = rman_sg_node - - if self.is_interactive and not ob.show_instancer_for_viewport: - rman_sg_node.sg_node.SetHidden(1) - elif not ob.show_instancer_for_render: - rman_sg_node.sg_node.SetHidden(1) - - if rman_type in ['MESH', 'POINTS']: - # Deal with any particles now. Particles are children to mesh nodes. - subframes = [] - if self.do_motion_blur: - subframes = scene_utils._get_subframes_(2, self.bl_scene) - self.motion_steps.update(subframes) - - if len(ob.particle_systems) > 0: - particles_group_db = '' - rman_sg_node.rman_sg_particle_group_node = self.rman_translators['GROUP'].export(None, particles_group_db) + if not ob.original.select_get(): + if parent: + if not parent.original.select_get(): + return False + else: + return False + if parent and not parent.original.select_get(): + return False - psys_translator = self.rman_translators['PARTICLES'] - for psys in ob.particle_systems: - psys_db_name = '%s' % psys.name - rman_sg_particles = psys_translator.export(ob, psys, psys_db_name) - if not rman_sg_particles: - continue - - psys_translator.set_motion_steps(rman_sg_particles, subframes) - psys_translator.update(ob, psys, rman_sg_particles) - - ob_psys = self.rman_particles.get(ob.original, dict()) - ob_psys[psys.settings.original] = rman_sg_particles - self.rman_particles[ob.original] = ob_psys - self.rman_objects[psys.settings.original] = rman_sg_particles - self.processed_obs.append(psys.settings.original) - rman_sg_node.rman_sg_particle_group_node.sg_node.AddChild(rman_sg_particles.sg_node) - - elif rman_type == 'EMPTY' and (ob.hide_render or ob.hide_viewport): - # Make sure empties that are hidden still go out. Children - # could still be visible - self._export_hidden_instance(ob, rman_sg_node) - return rman_sg_node - - - # motion blur - # we set motion steps for this object, even if it's not moving - # it could be moving as part of a particle system - mb_segs = -1 - mb_deform_segs = -1 - if self.do_motion_blur: - mb_segs = self.bl_scene.renderman.motion_segments - mb_deform_segs = self.bl_scene.renderman.deform_motion_segments - if ob.renderman.motion_segments_override: - mb_segs = ob.renderman.motion_segments - if mb_segs > 1: - subframes = scene_utils._get_subframes_(mb_segs, self.bl_scene) - rman_sg_node.motion_steps = subframes - self.motion_steps.update(subframes) - - if ob.renderman.motion_segments_override: - mb_deform_segs = ob.renderman.deform_motion_segments - - if mb_deform_segs > 1: - subframes = scene_utils._get_subframes_(mb_deform_segs, self.bl_scene) - rman_sg_node.deform_motion_steps = subframes - self.motion_steps.update(subframes) - - if rman_sg_node.is_transforming or rman_sg_node.is_deforming: - if mb_segs > 1 or mb_deform_segs > 1: - self.moving_objects[ob.name_full] = ob - - if mb_segs < 1: - rman_sg_node.is_transforming = False - if mb_deform_segs < 1: - rman_sg_node.is_deforming = False + return True - def export_defaultlight(self): - # Export a headlight light if needed - if not self.default_light: - self.default_light = self.sg_scene.CreateAnalyticLight('__defaultlight') - sg_node = self.rman.SGManager.RixSGShader("Light", 'PxrDistantLight' , "light") - self.default_light.SetLight(sg_node) - s_orientPxrLight = [-1.0, 0.0, -0.0, 0.0, - -0.0, -1.0, -0.0, 0.0, - 0.0, 0.0, -1.0, 0.0, - 0.0, 0.0, 0.0, 1.0] - self.default_light.SetOrientTransform(s_orientPxrLight) - - if self.render_default_light and not self.scene_any_lights: - self.default_light.SetHidden(0) + def get_rman_sg_instance(self, ob_inst, rman_sg_node, instance_parent, psys, create=True): + group_db_name = object_utils.get_group_db_name(ob_inst) + rman_parent_node = None + if psys and instance_parent: + rman_parent_node = self.get_rman_prototype(object_utils.prototype_key(instance_parent), ob=instance_parent, create=True) + if rman_parent_node: + if group_db_name in rman_parent_node.instances: + return rman_parent_node.instances[group_db_name] else: - self.default_light.SetHidden(1) + if group_db_name in rman_sg_node.instances: + return rman_sg_node.instances[group_db_name] - def _scene_has_lights(self): - # Determine if there are any lights in the scene - num_lights = len(scene_utils.get_all_lights(self.bl_scene, include_light_filters=False)) - return num_lights > 0 + rman_sg_group = None + if create: + rman_group_translator = self.rman_translators['GROUP'] + rman_sg_group = rman_group_translator.export(None, group_db_name) + rman_sg_group.sg_node.AddChild(rman_sg_node.sg_node) - def _export_hidden_instance(self, ob, rman_sg_node): - translator = self.rman_translators.get('EMPTY') - translator.export_object_attributes(ob, rman_sg_node) - self.attach_material(ob, rman_sg_node) - if ob.parent and object_utils._detect_primitive_(ob.parent) == 'EMPTY': - rman_empty_node = self.rman_objects.get(ob.parent.original) - if not rman_empty_node: - # Empty was not created. Export it. - parent = ob.parent - rman_empty_node = self.export_data_block(parent) - if not rman_empty_node: - return - rman_empty_node.sg_node.AddChild(rman_sg_node.sg_node) - else: - self.get_root_sg_node().AddChild(rman_sg_node.sg_node) - translator.export_transform(ob, rman_sg_node.sg_node) - if ob.renderman.export_as_coordsys: - self.get_root_sg_node().AddCoordinateSystem(rman_sg_node.sg_node) + if rman_parent_node: + # this is an instance that comes from a particle system + # add this instance to the rman_sg_node that owns the particle system + rman_parent_node.instances[group_db_name] = rman_sg_group + else: + rman_sg_node.instances[group_db_name] = rman_sg_group - def _export_instance(self, ob_inst, seg=None): - - group_db_name = object_utils.get_group_db_name(ob_inst) + return rman_sg_group + + def export_instance(self, ob_eval, ob_inst, rman_sg_node, rman_type, instance_parent, psys): rman_group_translator = self.rman_translators['GROUP'] - parent_sg_node = None - rman_sg_particles = None - psys = None - parent = None - if ob_inst.is_instance: - parent = ob_inst.parent - ob = ob_inst.instance_object - psys = ob_inst.particle_system - if psys: - # This object was instanced as part of a particle system. Add the object - # to particle system's owner' objects_instanced set. - parent_sg_node = self.rman_objects.get(parent.original, None) - if parent_sg_node: - parent_sg_node.objects_instanced.add(ob.original) - else: - #if parent.type == "EMPTY" and parent.is_instancer: - if parent.is_instancer: - parent_db_name = object_utils.get_db_name(parent) - parent_sg_node = self.rman_objects.get(parent.original, None) - if not parent_sg_node: - parent_sg_node = rman_group_translator.export(parent, parent_db_name) - self.rman_objects[parent.original] = parent_sg_node + rman_sg_group = self.get_rman_sg_instance(ob_inst, rman_sg_node, instance_parent, psys, create=True) + is_empty_instancer = False + if instance_parent: + is_empty_instancer = object_utils.is_empty_instancer(instance_parent) + + # Object attrs + translator = self.rman_translators.get(rman_type, None) + if translator: + if rman_sg_node.shared_attrs.GetNumParams() == 0: + # export the attributes for this object + translator.export_object_attributes(ob_eval, rman_sg_group) + rman_sg_node.shared_attrs.Inherit(rman_sg_group.sg_node.GetAttributes()) + else: + # the attributes of this object have already been exported + # just call SetAttributes + rman_sg_group.sg_node.SetAttributes(rman_sg_node.shared_attrs) + if is_empty_instancer: + translator.export_object_attributes(instance_parent, rman_sg_group, remove=False) + + translator.export_instance_attributes(ob_eval, rman_sg_group, ob_inst) + + # Add any particles necessary + if rman_sg_node.rman_sg_particle_group_node: + if (len(ob_eval.particle_systems) > 0) and ob_inst.show_particles: + rman_sg_group.sg_node.AddChild(rman_sg_node.rman_sg_particle_group_node.sg_node) + + # Attach a material + if is_empty_instancer and instance_parent.renderman.rman_material_override: + self.attach_material(instance_parent, rman_sg_group) + elif psys: + self.attach_particle_material(psys.settings, instance_parent, ob_eval, rman_sg_group) + rman_sg_group.bl_psys_settings = psys.settings.original else: - ob = ob_inst.object - - if ob.type in ('ARMATURE', 'CAMERA'): - return - - rman_type = object_utils._detect_primitive_(ob) - if rman_type == 'LIGHTFILTER': - # light filters are part of lights, so when light instances - # are exported, light filterrs should go along with them - return - - elif ob.type == "EMPTY" and ob.is_instancer: - rman_sg_node = self.rman_objects.get(ob.original, None) - if not rman_sg_node: - empty_db_name = object_utils.get_db_name(ob) - rman_sg_node = rman_group_translator.export(ob, empty_db_name) - self.rman_objects[ob.original] = rman_sg_node + self.attach_material(ob_eval, rman_sg_group) + + if object_utils.has_empty_parent(ob_eval): + # this object is a child of an empty. Add it to the empty. + ob_parent_eval = ob_eval.parent.evaluated_get(self.depsgraph) + parent_proto_key = object_utils.prototype_key(ob_eval.parent) + rman_empty_node = self.get_rman_prototype(parent_proto_key, ob=ob_parent_eval, create=True) + rman_sg_group.sg_node.SetInheritTransform(False) # we don't want to inherit the transform + rman_empty_node.sg_node.AddChild(rman_sg_group.sg_node) + elif is_empty_instancer: + parent_proto_key = object_utils.prototype_key(instance_parent) + rman_parent_node = self.get_rman_prototype(parent_proto_key, ob=instance_parent, create=True) + rman_sg_group.sg_node.SetInheritTransform(False) # we don't want to inherit the transform + rman_parent_node.sg_node.AddChild(rman_sg_group.sg_node) else: - if rman_type == 'EMPTY': - # this is just a regular empty object. - rman_sg_node = self.rman_objects.get(ob.original, None) - if rman_sg_node: - self._export_hidden_instance(ob, rman_sg_node) - return - - if rman_type == "META": - # only add the meta instance that matches the family name - if ob.name_full != object_utils.get_meta_family(ob): - return - - rman_sg_node = self.rman_objects.get(ob.original, None) - if not rman_sg_node: - return + self.get_root_sg_node().AddChild(rman_sg_group.sg_node) + + if rman_type == "META": + # meta/blobbies are already in world space. Their instances don't need to + # set a transform. + return rman_sg_group + + rman_group_translator.update_transform(ob_inst, rman_sg_group) + return rman_sg_group - translator = self.rman_translators.get(rman_type, None) - if not translator: - return - if group_db_name in rman_sg_node.instances: - # we've already added this instance - return - else: + def export_data_blocks(self, selected_objects=False, objects_list=False): + total = len(self.depsgraph.object_instances) + for i, ob_inst in enumerate(self.depsgraph.object_instances): + ob = ob_inst.object + rfb_log().debug(" Exported %d/%d instances... (%s)" % (i, total, ob.name)) + self.rman_render.stats_mgr.set_export_stats("Exporting instances",i/total) + if ob.type in ('ARMATURE', 'CAMERA'): + continue - if not ob.original in self.processed_obs: - translator.update(ob, rman_sg_node) - translator.export_object_primvars(ob, rman_sg_node) - self.processed_obs.append(ob.original) - - rman_sg_group = rman_group_translator.export(ob, group_db_name) - if ob.is_instancer and ob.instance_type != 'NONE': - rman_sg_group.is_instancer = ob.is_instancer - if rman_sg_node.sg_node is None: - # add the group to the root anyways - db_name = object_utils.get_db_name(ob, rman_type=rman_type) - rman_sg_group.db_name = db_name - self.get_root_sg_node().AddChild(rman_sg_group.sg_node) - self.rman_objects[ob.original] = rman_sg_group - return - - rman_sg_group.sg_node.AddChild(rman_sg_node.sg_node) - rman_sg_group.rman_sg_node_instance = rman_sg_node - - if rman_sg_node.rman_sg_particle_group_node: - if (len(ob.particle_systems) > 0) and ob_inst.show_particles: - rman_sg_group.sg_node.AddChild(rman_sg_node.rman_sg_particle_group_node.sg_node) - - if ob.parent and object_utils._detect_primitive_(ob.parent) == 'EMPTY': - # this object is a child of an empty. Add it to the empty. - rman_empty_node = self.rman_objects.get(ob.parent.original) - rman_sg_group.sg_node.SetInheritTransform(False) # we don't want to inherit the transform - rman_empty_node.sg_node.AddChild(rman_sg_group.sg_node) - else: - self.get_root_sg_node().AddChild(rman_sg_group.sg_node) + if selected_objects and not self.is_instance_selected(ob_inst): + continue - # add this instance to rman_sg_node - rman_sg_node.instances[group_db_name] = rman_sg_group + # only export these objects + if objects_list and ob.original not in objects_list: + continue - # object attrs - translator.export_object_attributes(ob, rman_sg_group) - translator.export_object_id(ob, rman_sg_group, ob_inst) + if not self.check_visibility(ob_inst): + rfb_log().debug(" Object (%s) not visible" % (ob.name)) + continue - # attach material - if psys: - self.attach_particle_material(psys.settings, parent, ob, rman_sg_group) - rman_sg_group.bl_psys_settings = psys.settings.original - else: - self.attach_material(ob, rman_sg_group) + ob_eval = ob.evaluated_get(self.depsgraph) + psys = None + instance_parent = None + proto_key = object_utils.prototype_key(ob_inst) + if ob_inst.is_instance: + psys = ob_inst.particle_system + instance_parent = ob_inst.parent - # check local view - if self.is_interactive: - if parent: - if not parent.visible_in_viewport_get(self.context.space_data): - rman_sg_group.sg_node.SetHidden(1) - else: - rman_sg_group.sg_node.SetHidden(-1) - else: - if not ob.visible_in_viewport_get(self.context.space_data): - rman_sg_group.sg_node.SetHidden(1) - else: - rman_sg_group.sg_node.SetHidden(-1) - - if rman_type == "META": - # meta/blobbies are already in world space. Their instances don't need to - # set a transform. - return + rman_type = object_utils._detect_primitive_(ob_eval) + rman_sg_node = self.get_rman_prototype(proto_key, ob=ob_eval, create=True) + if not rman_sg_node: + continue - if rman_sg_node.is_transforming: - rman_group_translator.update_transform_num_samples(rman_sg_group, rman_sg_node.motion_steps ) - rman_group_translator.update_transform_sample(ob_inst, rman_sg_group, 0, seg ) - elif psys and self.do_motion_blur: - rman_group_translator.update_transform_num_samples(rman_sg_group, rman_sg_node.motion_steps ) - rman_group_translator.update_transform_sample(ob_inst, rman_sg_group, 0, seg ) - else: - rman_group_translator.update_transform(ob_inst, rman_sg_group) + if rman_type == 'LIGHT': + self.check_solo_light(rman_sg_node, ob_eval) - def export_instances(self, obj_selected=None): - total = len(self.depsgraph.object_instances) - obj_selected_names = [] - if obj_selected: - obj_selected_names = [o.name for o in obj_selected] - for i, ob_inst in enumerate(self.depsgraph.object_instances): - if obj_selected: - objFound = False + if rman_type in object_utils._RMAN_NO_INSTANCES_: + continue - if ob_inst.is_instance: - if ob_inst.instance_object.name in obj_selected_names: - objFound = True - elif ob_inst.object.name in obj_selected_names: - objFound = True + self.export_instance(ob_eval, ob_inst, rman_sg_node, rman_type, instance_parent, psys) - if not objFound: - continue + def export_data_block(self, proto_key, ob): + rman_type = object_utils._detect_primitive_(ob) - #if not self.is_interactive and not ob_inst.show_self: - # continue + if rman_type == "META": + # only add the meta instance that matches the family name + if ob.name_full != object_utils.get_meta_family(ob): + return None + + if proto_key in self.rman_prototypes: + return self.rman_prototypes[proto_key] + + translator = self.rman_translators.get(rman_type, None) + if not translator: + return None + + rman_sg_node = None + db_name = object_utils.get_db_name(ob) + rman_sg_node = translator.export(ob, db_name) + if not rman_sg_node: + return None + rman_sg_node.rman_type = rman_type + self.rman_prototypes[proto_key] = rman_sg_node + + # motion blur + # we set motion steps for this object, even if it's not moving + # it could be moving as part of a particle system + mb_segs = -1 + mb_deform_segs = -1 + if self.do_motion_blur: + mb_segs = self.bl_scene.renderman.motion_segments + mb_deform_segs = self.bl_scene.renderman.deform_motion_segments + if ob.renderman.motion_segments_override: + mb_segs = ob.renderman.motion_segments + if mb_segs > 1: + subframes = scene_utils._get_subframes_(mb_segs, self.bl_scene) + rman_sg_node.motion_steps = subframes + self.motion_steps.update(subframes) + + if ob.renderman.motion_segments_override: + mb_deform_segs = ob.renderman.deform_motion_segments + + if mb_deform_segs > 1: + subframes = scene_utils._get_subframes_(mb_deform_segs, self.bl_scene) + rman_sg_node.deform_motion_steps = subframes + self.motion_steps.update(subframes) + + if rman_sg_node.is_transforming or rman_sg_node.is_deforming: + if mb_segs > 1 or mb_deform_segs > 1: + self.moving_objects[ob.name_full] = ob + + if mb_segs < 1: + rman_sg_node.is_transforming = False + if mb_deform_segs < 1: + rman_sg_node.is_deforming = False + + translator.update(ob, rman_sg_node) + + if len(ob.particle_systems) > 0: + # Deal with any particles now. + subframes = [] + if self.do_motion_blur: + subframes = scene_utils._get_subframes_(2, self.bl_scene) + self.motion_steps.update(subframes) - self._export_instance(ob_inst) - self.rman_render.stats_mgr.set_export_stats("Exporting instances", i/total) - - rfb_log().debug(" Exported %d/%d instances..." % (i, total)) + particles_group_db = '' + rman_sg_node.rman_sg_particle_group_node = self.rman_translators['GROUP'].export(None, particles_group_db) - def attach_material(self, ob, rman_sg_node): - mat = object_utils.get_active_material(ob) - if mat: - rman_sg_material = self.rman_materials.get(mat.original, None) - if rman_sg_material and rman_sg_material.sg_node: - scenegraph_utils.set_material(rman_sg_node.sg_node, rman_sg_material.sg_node) - rman_sg_node.is_meshlight = rman_sg_material.has_meshlight + psys_translator = self.rman_translators['PARTICLES'] + for psys in ob.particle_systems: + psys_db_name = '%s' % psys.name + rman_sg_particles = psys_translator.export(ob, psys, psys_db_name) + if not rman_sg_particles: + continue - def attach_particle_material(self, psys_settings, parent, ob, group): - # This function should only be used by particle instancing. - # For emitters and hair, the material attachment is done in either - # the emitter translator or hair translator directly + psys_translator.set_motion_steps(rman_sg_particles, subframes) + psys_translator.update(ob, psys, rman_sg_particles) - if not object_utils.is_particle_instancer(psys=None, particle_settings=psys_settings): - return + ob_psys = self.rman_particles.get(proto_key, dict()) + ob_psys[psys.settings.original] = rman_sg_particles + self.rman_particles[proto_key] = ob_psys + rman_sg_node.rman_sg_particle_group_node.sg_node.AddChild(rman_sg_particles.sg_node) - if psys_settings.renderman.override_instance_material: - mat_idx = psys_settings.material - 1 - if mat_idx < len(parent.material_slots): - mat = parent.material_slots[mat_idx].material - rman_sg_material = self.rman_materials.get(mat.original, None) - if rman_sg_material: - scenegraph_utils.set_material(group.sg_node, rman_sg_material.sg_node) - else: - mat = object_utils.get_active_material(ob) - if mat: - rman_sg_material = self.rman_materials.get(mat.original, None) - if rman_sg_material and rman_sg_material.sg_node: - scenegraph_utils.set_material(group.sg_node, rman_sg_material.sg_node) - group.is_meshlight = rman_sg_material.has_meshlight + if rman_type == 'EMPTY': + # If this is an empty, just export it as a coordinate system + # along with any instance attributes/materials necessary + self._export_hidden_instance(ob, rman_sg_node) + return rman_sg_node + elif rman_type == 'EMPTY_INSTANCER': + self.get_root_sg_node().AddChild(rman_sg_node.sg_node) - def export_instances_motion(self, obj_selected=None): + return rman_sg_node + + def export_instances_motion(self, selected_objects=False): origframe = self.bl_scene.frame_current mb_segs = self.bl_scene.renderman.motion_segments - origframe = self.bl_scene.frame_current + origframe = self.bl_scene.frame_current motion_steps = sorted(list(self.motion_steps)) first_sample = False - delta = -motion_steps[0] + delta = 0.0 + if len(motion_steps) > 0: + delta = -motion_steps[0] + psys_translator = self.rman_translators['PARTICLES'] + rman_group_translator = self.rman_translators['GROUP'] for samp, seg in enumerate(motion_steps): first_sample = (samp == 0) if seg < 0.0: self.rman_render.bl_engine.frame_set(origframe - 1, subframe=1.0 + seg) else: - self.rman_render.bl_engine.frame_set(origframe, subframe=seg) + self.rman_render.bl_engine.frame_set(origframe, subframe=seg) self.depsgraph.update() time_samp = seg + delta # get the normlized version of the segment total = len(self.depsgraph.object_instances) objFound = False - + # update camera if not first_sample and self.main_camera.is_transforming and seg in self.main_camera.motion_steps: cam_translator = self.rman_translators['CAMERA'] @@ -970,144 +854,218 @@ def export_instances_motion(self, obj_selected=None): break cam_translator.update_transform(self.depsgraph.scene_eval.camera, self.main_camera, idx, time_samp) - for i, ob_inst in enumerate(self.depsgraph.object_instances): - if obj_selected: - if objFound: - break - - if ob_inst.is_instance: - if ob_inst.instance_object.name == obj_selected: - objFound = True - elif ob_inst.object.name == obj_selected.name: - objFound = True - - if not objFound: - continue - - if not ob_inst.show_self: - continue + rfb_log().debug(" Export Sample: %i" % samp) + for i, ob_inst in enumerate(self.depsgraph.object_instances): + if selected_objects and not self.is_instance_selected(ob_inst): + continue - if first_sample: - # for the first motion sample use _export_instance() - self._export_instance(ob_inst, seg=time_samp) - self.rman_render.stats_mgr.set_export_stats("Exporting instances (%f)" % seg, i/total) - continue + if not self.check_visibility(ob_inst): + continue - rman_group_translator = self.rman_translators['GROUP'] psys = None + ob = ob_inst.object.evaluated_get(self.depsgraph) + proto_key = object_utils.prototype_key(ob_inst) + rfb_log().debug(" Exported %d/%d motion instances... (%s)" % (i, total, ob.name)) + self.rman_render.stats_mgr.set_export_stats("Exporting motion instances (%d) " % samp ,i/total) + instance_parent = None + rman_parent_node = None if ob_inst.is_instance: - ob = ob_inst.instance_object.original psys = ob_inst.particle_system - else: - ob = ob_inst.object + instance_parent = ob_inst.parent + rman_parent_node = self.get_rman_prototype(object_utils.prototype_key(instance_parent)) - if ob.name_full not in self.moving_objects and not psys: + rman_type = object_utils._detect_primitive_(ob) + if rman_type in object_utils._RMAN_NO_INSTANCES_: continue - if ob.type not in ['MESH']: - continue - - group_db_name = object_utils.get_group_db_name(ob_inst) + # check particles for motion + ''' + for psys in ob.particle_systems: + ob_psys = self.rman_particles.get(proto_key, None) + if not ob_psys: + continue + rman_sg_particles = ob_psys.get(psys.settings.original, None) + if not rman_sg_particles: + continue + if not seg in rman_sg_particles.motion_steps: + continue + idx = 0 + for i, s in enumerate(rman_sg_particles.motion_steps): + if s == seg: + idx = i + break + psys_translator.export_deform_sample(rman_sg_particles, ob, psys, idx) + ''' + + # object is not moving and not part of a particle system + if ob.name_full not in self.moving_objects and not psys: + continue - rman_sg_node = self.rman_objects.get(ob.original, None) + rman_sg_node = self.get_rman_prototype(proto_key, ob=ob) if not rman_sg_node: continue - - if not seg in rman_sg_node.motion_steps: - continue - idx = 0 - for i, s in enumerate(rman_sg_node.motion_steps): - if s == seg: - idx = i - break - - if rman_sg_node.is_transforming or psys: - rman_sg_group = rman_sg_node.instances.get(group_db_name, None) - if rman_sg_group: - rman_group_translator.update_transform_num_samples(rman_sg_group, rman_sg_node.motion_steps ) # should have been set in _export_instances() - rman_group_translator.update_transform_sample( ob_inst, rman_sg_group, idx, time_samp) - - self.rman_render.stats_mgr.set_export_stats("Exporting instances (%f)" % seg, i/total) - - for ob_original,rman_sg_node in self.rman_objects.items(): - ob = ob_original.evaluated_get(self.depsgraph) - psys_translator = self.rman_translators['PARTICLES'] - particle_systems = getattr(ob, 'particle_systems', list()) - for psys in particle_systems: - ob_psys = self.rman_particles.get(ob.original, dict()) - rman_sg_particles = ob_psys.get(psys.settings.original, None) - if rman_sg_particles: - if not seg in rman_sg_particles.motion_steps: - continue - idx = 0 - for i, s in enumerate(rman_sg_node.motion_steps): - if s == seg: - idx = i - break - psys_translator.export_deform_sample(rman_sg_particles, ob, psys, idx) + # transformation blur + if seg in rman_sg_node.motion_steps: + idx = 0 + for i, s in enumerate(rman_sg_node.motion_steps): + if s == seg: + idx = i + break + + if rman_sg_node.is_transforming or psys: + group_db_name = object_utils.get_group_db_name(ob_inst) + if instance_parent: + rman_sg_group = rman_parent_node.instances.get(group_db_name, None) + else: + rman_sg_group = rman_sg_node.instances.get(group_db_name, None) + if rman_sg_group: + if first_sample: + rman_group_translator.update_transform_num_samples(rman_sg_group, rman_sg_node.motion_steps ) + rman_group_translator.update_transform_sample( ob_inst, rman_sg_group, idx, time_samp) + # deformation blur if rman_sg_node.is_deforming and seg in rman_sg_node.deform_motion_steps: rman_type = rman_sg_node.rman_type - if rman_type in ['MESH', 'FLUID']: + if rman_type in ['MESH', 'FLUID', 'CURVES']: translator = self.rman_translators.get(rman_type, None) if translator: - idx = 0 + deform_idx = 0 for i, s in enumerate(rman_sg_node.deform_motion_steps): if s == seg: - idx = i - break - translator.export_deform_sample(rman_sg_node, ob, idx) + deform_idx = i + break + translator.export_deform_sample(rman_sg_node, ob, deform_idx) + + self.rman_render.bl_engine.frame_set(origframe, subframe=0) + rfb_log().debug(" Finished exporting motion instances") + self.rman_render.stats_mgr.set_export_stats("Finished exporting motion instances", 100) + + def export_defaultlight(self): + # Export a headlight light if needed + if not self.default_light: + self.default_light = self.sg_scene.CreateAnalyticLight('__defaultlight') + sg_node = self.rman.SGManager.RixSGShader("Light", 'PxrDistantLight' , "light") + self.default_light.SetLight(sg_node) + s_orientPxrLight = [-1.0, 0.0, -0.0, 0.0, + -0.0, -1.0, -0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + self.default_light.SetOrientTransform(s_orientPxrLight) + + if self.render_default_light and not self.scene_any_lights: + self.default_light.SetHidden(0) + else: + self.default_light.SetHidden(1) + + def _scene_has_lights(self): + # Determine if there are any lights in the scene + num_lights = len(scene_utils.get_all_lights(self.bl_scene, include_light_filters=False)) + return num_lights > 0 + + def get_rman_prototype(self, proto_key, ob=None, create=False): + if proto_key in self.rman_prototypes: + return self.rman_prototypes[proto_key] + + if not create: + return None + + if not ob: + return None + + rman_sg_node = self.export_data_block(proto_key, ob) + return rman_sg_node + + def get_rman_particles(self, proto_key, psys, ob, create=True): + psys_translator = self.rman_translators['PARTICLES'] + group_translator = self.rman_translators['GROUP'] + ob_psys = self.rman_particles.get(proto_key, dict()) + rman_sg_particles = ob_psys.get(psys.settings.original, None) + if not rman_sg_particles and create: + psys_db_name = '%s' % psys.name + rman_sg_particles = psys_translator.export(ob, psys, psys_db_name) + ob_psys[psys.settings.original] = rman_sg_particles + self.rman_particles[proto_key] = ob_psys + rman_sg_node = self.get_rman_prototype(proto_key) + if rman_sg_node: + if not rman_sg_node.rman_sg_particle_group_node: + particles_group_db = '' + rman_sg_node.rman_sg_particle_group_node = group_translator.export(None, particles_group_db) + rman_sg_node.rman_sg_particle_group_node.sg_node.AddChild(rman_sg_particles.sg_node) + return rman_sg_particles + + def _export_hidden_instance(self, ob, rman_sg_node): + translator = self.rman_translators.get('EMPTY') + translator.export_object_attributes(ob, rman_sg_node) + self.attach_material(ob, rman_sg_node) + if object_utils.has_empty_parent(ob): + parent_proto_key = object_utils.prototype_key(ob.parent) + ob_parent_eval = ob.parent.evaluated_get(self.depsgraph) + rman_empty_node = self.get_rman_prototype(parent_proto_key, ob=ob_parent_eval, create=True) + rman_empty_node.sg_node.AddChild(rman_sg_node.sg_node) + else: + self.get_root_sg_node().AddChild(rman_sg_node.sg_node) + translator.export_transform(ob, rman_sg_node.sg_node) + if ob.renderman.export_as_coordsys: + self.get_root_sg_node().AddCoordinateSystem(rman_sg_node.sg_node) - self.rman_render.bl_engine.frame_set(origframe, subframe=0) + def attach_material(self, ob, rman_sg_node): + mat = object_utils.get_active_material(ob) + if mat: + rman_sg_material = self.rman_materials.get(mat.original, None) + if rman_sg_material and rman_sg_material.sg_node: + scenegraph_utils.set_material(rman_sg_node.sg_node, rman_sg_material.sg_node) + rman_sg_node.is_meshlight = rman_sg_material.has_meshlight + + def attach_particle_material(self, psys_settings, parent, ob, group): + # This function should only be used by particle instancing. + # For emitters and hair, the material attachment is done in either + # the emitter translator or hair translator directly + + if not object_utils.is_particle_instancer(psys=None, particle_settings=psys_settings): + return + + if psys_settings.renderman.override_instance_material: + mat_idx = psys_settings.material - 1 + if mat_idx < len(parent.material_slots): + mat = parent.material_slots[mat_idx].material + rman_sg_material = self.rman_materials.get(mat.original, None) + if rman_sg_material: + scenegraph_utils.set_material(group.sg_node, rman_sg_material.sg_node) + else: + mat = object_utils.get_active_material(ob) + if mat: + rman_sg_material = self.rman_materials.get(mat.original, None) + if rman_sg_material and rman_sg_material.sg_node: + scenegraph_utils.set_material(group.sg_node, rman_sg_material.sg_node) + group.is_meshlight = rman_sg_material.has_meshlight def check_light_local_view(self, ob, rman_sg_node): if self.is_interactive and self.context.space_data: - if not ob.visible_in_viewport_get(self.context.space_data): + if not ob.visible_in_viewport_get(self.context.space_data): rman_sg_node.sg_node.SetHidden(1) return True - return False + return False - def check_solo_light(self): - if self.bl_scene.renderman.solo_light: - for light_ob in scene_utils.get_all_lights(self.bl_scene, include_light_filters=False): - rman_sg_node = self.rman_objects.get(light_ob.original, None) - if not rman_sg_node: - continue - rm = light_ob.renderman - if not rm: - continue - if rm.solo: - rman_sg_node.sg_node.SetHidden(0) - else: - rman_sg_node.sg_node.SetHidden(1) - else: - for light_ob in scene_utils.get_all_lights(self.bl_scene, include_light_filters=False): - rman_sg_node = self.rman_objects.get(light_ob.original, None) - if not rman_sg_node: - continue - rm = light_ob.renderman - if not rm: - continue - - if self.check_light_local_view(light_ob, rman_sg_node): - return - - if self.is_interactive: - if not light_ob.hide_get(): - rman_sg_node.sg_node.SetHidden(rm.mute) - else: - rman_sg_node.sg_node.SetHidden(1) - else: - rman_sg_node.sg_node.SetHidden(rm.mute) + def check_solo_light(self, rman_sg_node, ob): + if not self.scene_solo_light: + rman_sg_node.sg_node.SetHidden(ob.renderman.mute) + else: + rm = ob.renderman + if not rm: + return + if rm.solo: + rman_sg_node.sg_node.SetHidden(0) + else: + rman_sg_node.sg_node.SetHidden(1) def export_searchpaths(self): - # TODO + # TODO # RMAN_ARCHIVEPATH, # RMAN_DISPLAYPATH, RMAN_PROCEDURALPATH, and RMAN_DSOPATH (combines procedurals and displays) - + # get cycles shader directory cycles_shader_dir = filepath_utils.get_cycles_shader_path() @@ -1165,7 +1123,7 @@ def export_hider(self): options.SetInteger(self.rman.Tokens.Rix.k_hider_minsamples, rm.ipr_hider_minSamples) options.SetInteger(self.rman.Tokens.Rix.k_hider_maxsamples, rm.ipr_hider_maxSamples) pv = rm.ipr_ri_pixelVariance - + # force incremental when checkpointing if rm.enable_checkpoint: options.SetInteger(self.rman.Tokens.Rix.k_hider_incremental, 1) @@ -1184,7 +1142,7 @@ def export_hider(self): if anyDenoise: options.SetString(self.rman.Tokens.Rix.k_hider_pixelfiltermode, 'importance') - self.sg_scene.SetOptions(options) + self.sg_scene.SetOptions(options) def export_global_options(self): rm = self.bl_scene.renderman @@ -1192,23 +1150,7 @@ def export_global_options(self): # set any properties marked riopt in the config file for prop_name, meta in rm.prop_meta.items(): - if 'riopt' not in meta: - continue - - val = getattr(rm, prop_name) - ri_name = meta['riopt'] - is_array = False - array_len = -1 - if 'arraySize' in meta: - is_array = True - array_len = meta['arraySize'] - if type(val) == str and val.startswith('['): - val = eval(val) - - param_type = meta['renderman_type'] - if param_type == "string": - val = string_utils.expand_string(val, asFilePath=True) - property_utils.set_rix_param(options, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=rm) + property_utils.set_rioption_bl_prop(options, prop_name, meta, rm) # threads if not self.external_render: @@ -1228,8 +1170,8 @@ def export_global_options(self): options.SetString(self.rman.Tokens.Rix.k_checkpoint_exitat, rm.checkpoint_exitat) options.SetInteger(self.rman.Tokens.Rix.k_checkpoint_asfinal, int(rm.checkpoint_asfinal)) - - # Set frame number + + # Set frame number options.SetInteger(self.rman.Tokens.Rix.k_Ri_Frame, self.bl_scene.frame_current) # Always turn off xml stats when in interactive @@ -1241,13 +1183,13 @@ def export_global_options(self): bucket_orderorigin = [] if rm.enable_checkpoint and not self.is_interactive: bucket_order = 'horizontal' - + elif rm.opt_bucket_order == 'spiral': settings = self.bl_scene.render if rm.opt_bucket_sprial_x <= settings.resolution_x and rm.opt_bucket_sprial_y <= settings.resolution_y: if rm.opt_bucket_sprial_x == -1: - halfX = settings.resolution_x / 2 + halfX = settings.resolution_x / 2 bucket_orderorigin = [int(halfX), rm.opt_bucket_sprial_y] elif rm.opt_bucket_sprial_y == -1: @@ -1255,7 +1197,7 @@ def export_global_options(self): bucket_orderorigin = [rm.opt_bucket_sprial_y, int(halfY)] else: bucket_orderorigin = [rm.opt_bucket_sprial_x, rm.opt_bucket_sprial_y] - + options.SetString(self.rman.Tokens.Rix.k_bucket_order, bucket_order) if bucket_orderorigin: options.SetFloatArray(self.rman.Tokens.Rix.k_bucket_orderorigin, bucket_orderorigin, 2) @@ -1272,11 +1214,16 @@ def export_global_options(self): elif rm.shutter_timing == 'FRAME_OPEN': shutter_open, shutter_close = 0, shutter_interval ''' - shutter_open, shutter_close = 0, shutter_interval - options.SetFloatArray(self.rman.Tokens.Rix.k_Ri_Shutter, (shutter_open, shutter_close), 2) + shutter_open, shutter_close = 0, shutter_interval + options.SetFloatArray(self.rman.Tokens.Rix.k_Ri_Shutter, (shutter_open, shutter_close), 2) # dirmaps dirmaps = '' + prefs_dirmaps = get_pref('rman_tractor_dirmaps', []) + for dirmap in prefs_dirmaps: + d = "[ \"%s\" \"%s\" \"%s\"]" % (dirmap.zone, dirmap.from_path, dirmap.to_path) + dirmaps += d + for k in rfb_config['dirmaps']: dirmap = rfb_config['dirmaps'][k] d = "[ \"%s\" \"%s\" \"%s\"]" % (dirmap['zone'], dirmap['from'], dirmap['to']) @@ -1289,8 +1236,9 @@ def export_global_options(self): ociocolorspacename = color_manager_blender.get_colorspace_name() options.SetString('user:ocioconfigpath', ocioconfig) options.SetString('user:ociocolorspacename', ociocolorspacename) + options.SetInteger('user:ocioenabled', 1 if ocioconfig else 0) - self.sg_scene.SetOptions(options) + self.sg_scene.SetOptions(options) def export_integrator(self): world = self.bl_scene.world @@ -1304,14 +1252,14 @@ def export_integrator(self): else: integrator_sg = self.rman.SGManager.RixSGShader("Integrator", "PxrPathTracer", "integrator") - self.sg_scene.SetIntegrator(integrator_sg) + self.sg_scene.SetIntegrator(integrator_sg) def export_cameras(self, bl_cameras): main_cam = self.depsgraph.scene_eval.camera cam_translator = self.rman_translators['CAMERA'] - + if self.is_viewport_render: db_name = 'main_camera' self.main_camera = cam_translator.export(None, db_name) @@ -1321,23 +1269,19 @@ def export_cameras(self, bl_cameras): # add camera so we don't mistake it for a new obj if main_cam: self.rman_cameras[main_cam.original] = self.main_camera - self.rman_objects[main_cam.original] = self.main_camera - - self.processed_obs.append(main_cam.original) else: if self.is_interactive: main_cam = self.context.space_data.camera db_name = object_utils.get_db_name(main_cam) rman_sg_camera = cam_translator.export(main_cam, db_name) - self.main_camera = rman_sg_camera - if main_cam: - self.rman_cameras[main_cam.original] = rman_sg_camera - self.rman_objects[main_cam.original] = rman_sg_camera - + self.main_camera = rman_sg_camera + if main_cam: + self.rman_cameras[main_cam.original] = rman_sg_camera + # resolution - cam_translator._update_render_resolution(main_cam, self.main_camera) - - self.sg_scene.Root().AddChild(rman_sg_camera.sg_node) + cam_translator._update_render_resolution(main_cam, self.main_camera) + + self.sg_scene.Root().AddChild(rman_sg_camera.sg_node) # export all other scene cameras for cam in bl_cameras: @@ -1346,22 +1290,20 @@ def export_cameras(self, bl_cameras): continue if cam == main_cam: if self.main_camera.is_transforming: - self.motion_steps.update(self.main_camera.motion_steps) + self.motion_steps.update(self.main_camera.motion_steps) continue - + db_name = object_utils.get_db_name(ob) rman_sg_camera = cam_translator._export_render_cam(ob, db_name) self.rman_cameras[cam.original] = rman_sg_camera - - self.rman_objects[cam.original] = rman_sg_camera - + self.sg_scene.Root().AddChild(rman_sg_camera.sg_node) self.sg_scene.Root().AddCoordinateSystem(rman_sg_camera.sg_node) # For now, make the main camera the 'primary' dicing camera self.main_camera.sg_camera_node.SetRenderable(1) - self.sg_scene.Root().AddCoordinateSystem(self.main_camera.sg_node) + self.sg_scene.Root().AddCoordinateSystem(self.main_camera.sg_node) def export_displayfilters(self): rm = self.bl_scene.renderman @@ -1377,7 +1319,7 @@ def export_displayfilters(self): self.world_df_node = self.rman.SGManager.RixSGShader("DisplayFilter", "PxrBackgroundDisplayFilter", "__rman_world_df") params = self.world_df_node.params params.SetColor("backgroundColor", self.bl_scene.world.color[:3]) - self.sg_scene.SetDisplayFilter([self.world_df_node]) + self.sg_scene.SetDisplayFilter([self.world_df_node]) return for bl_df_node in shadergraph_utils.find_displayfilter_nodes(world): @@ -1394,7 +1336,7 @@ def export_displayfilters(self): rman_sg_node = RmanSgNode(self, rman_df_node, "") property_utils.property_group_to_rixparams(bl_df_node, rman_sg_node, rman_df_node, ob=world) display_filter_names.append(df_name) - displayfilters_list.append(rman_df_node) + displayfilters_list.append(rman_df_node) if len(display_filter_names) > 1: df_name = "rman_displayfilter_combiner" @@ -1403,11 +1345,11 @@ def export_displayfilters(self): params.SetDisplayFilterReferenceArray("filter", display_filter_names, len(display_filter_names)) displayfilters_list.append(df_node) - self.sg_scene.SetDisplayFilter(displayfilters_list) + self.sg_scene.SetDisplayFilter(displayfilters_list) def export_samplefilters(self, sel_chan_name=None): rm = self.bl_scene.renderman - sample_filter_names = [] + sample_filter_names = [] samplefilters_list = list() if rm.do_holdout_matte != "OFF": @@ -1421,7 +1363,7 @@ def export_samplefilters(self, sel_chan_name=None): params.SetString("shadowAov", "holdoutMatte") sample_filter_names.append("rm_PxrShadowFilter_shadows") - samplefilters_list.append(sf_node) + samplefilters_list.append(sf_node) world = self.bl_scene.world @@ -1434,15 +1376,15 @@ def export_samplefilters(self, sel_chan_name=None): rman_sg_node = RmanSgNode(self, rman_sf_node, "") property_utils.property_group_to_rixparams(bl_sf_node, rman_sg_node, rman_sf_node, ob=world) sample_filter_names.append(sf_name) - samplefilters_list.append(rman_sf_node) + samplefilters_list.append(rman_sf_node) if sel_chan_name: sf_name = '__RMAN_VIEWPORT_CHANNEL_SELECT__' rman_sel_chan_node = self.rman.SGManager.RixSGShader("SampleFilter", "PxrCopyAOVSampleFilter", sf_name) params = rman_sel_chan_node.params - params.SetString("readAov", sel_chan_name) + params.SetString("readAov", sel_chan_name) sample_filter_names.append(sf_name) - samplefilters_list.append(rman_sel_chan_node) + samplefilters_list.append(rman_sel_chan_node) if len(sample_filter_names) > 1: @@ -1453,7 +1395,7 @@ def export_samplefilters(self, sel_chan_name=None): samplefilters_list.append(sf_node) - self.sg_scene.SetSampleFilter(samplefilters_list) + self.sg_scene.SetSampleFilter(samplefilters_list) def export_bake_displays(self): rm = self.bl_scene.renderman @@ -1463,7 +1405,7 @@ def export_bake_displays(self): cams_to_dspys = dict() dspys_dict = display_utils.get_dspy_dict(self) - + for chan_name, chan_params in dspys_dict['channels'].items(): chan_type = chan_params['channelType']['value'] chan_source = chan_params['channelSource']['value'] @@ -1477,7 +1419,7 @@ def export_bake_displays(self): displaychannel = self.rman.SGManager.RixSGDisplayChannel(chan_type, chan_name) if chan_source: if "lpe" in chan_source: - displaychannel.params.SetString(self.rman.Tokens.Rix.k_source, '%s %s' % (chan_type, chan_source)) + displaychannel.params.SetString(self.rman.Tokens.Rix.k_source, '%s %s' % (chan_type, chan_source)) else: displaychannel.params.SetString(self.rman.Tokens.Rix.k_source, chan_source) @@ -1489,7 +1431,7 @@ def export_bake_displays(self): displaychannel.params.SetFloatArray("filterwidth", chan_filterwidth, 2 ) if chan_statistics and chan_statistics != 'none': - displaychannel.params.SetString("statistics", chan_statistics) + displaychannel.params.SetString("statistics", chan_statistics) displaychannels.append(displaychannel) # baking requires we only do one channel per display. So, we create a new display @@ -1503,12 +1445,12 @@ def export_bake_displays(self): if not dspy_params['bake_mode']: # if bake is off for this aov, just render to the null display driver dspy_file_name = dspy_params['filePath'] - display = self.rman.SGManager.RixSGShader("Display", "null", dspy_file_name) + display = self.rman.SGManager.RixSGShader("Display", "null", dspy_file_name) channels = ','.join(channels) display.params.SetString("mode", channels) cam_dspys = cams_to_dspys.get(self.main_camera, list()) cam_dspys.append(display) - cams_to_dspys[self.main_camera] = cam_dspys + cams_to_dspys[self.main_camera] = cam_dspys else: for chan in channels: @@ -1522,7 +1464,7 @@ def export_bake_displays(self): tokens = os.path.splitext(dspy_file_name) if tokens[1] == '': token_dict = {'aov': dspy} - dspy_file_name = string_utils.expand_string('%s.' % dspy_file_name, + dspy_file_name = string_utils.expand_string('%s.' % dspy_file_name, display=display_driver, token_dict=token_dict ) @@ -1539,7 +1481,7 @@ def export_bake_displays(self): if display_driver in ['deepexr', 'openexr']: if rm.use_metadata: display_utils.export_metadata(self.bl_scene, display.params) - + camera = dspy_params['camera'] if camera is None: cam_dspys = cams_to_dspys.get(self.main_camera, list()) @@ -1565,7 +1507,7 @@ def export_bake_displays(self): cam_sg_node.sg_camera_node.SetRenderable(2) cam_sg_node.sg_camera_node.SetDisplay(cam_dspys) - self.sg_scene.SetDisplayChannel(displaychannels) + self.sg_scene.SetDisplayChannel(displaychannels) def export_displays(self): rm = self.bl_scene.renderman @@ -1591,7 +1533,7 @@ def export_displays(self): displaychannel = self.rman.SGManager.RixSGDisplayChannel(chan_type, chan_name) if chan_source and chan_source != '': if "lpe" in chan_source: - displaychannel.params.SetString(self.rman.Tokens.Rix.k_source, '%s %s' % (chan_type, chan_source)) + displaychannel.params.SetString(self.rman.Tokens.Rix.k_source, '%s %s' % (chan_type, chan_source)) else: displaychannel.params.SetString(self.rman.Tokens.Rix.k_source, '%s' % (chan_source)) @@ -1604,7 +1546,7 @@ def export_displays(self): displaychannel.params.SetFloatArray("filterwidth", chan_filterwidth, 2 ) if chan_statistics and chan_statistics != 'none': - displaychannel.params.SetString("statistics", chan_statistics) + displaychannel.params.SetString("statistics", chan_statistics) displaychannels.append(displaychannel) for dspy,dspy_params in dspys_dict['displays'].items(): @@ -1617,12 +1559,12 @@ def export_displays(self): display.params.Inherit(dspydriver_params) display.params.SetString("mode", channels) if display_driver == "it": - dspy_info = display_utils.make_dspy_info(self.bl_scene) + dspy_info = display_utils.make_dspy_info(self.bl_scene, self.is_interactive) port = self.rman_render.it_port dspy_callback = "dspyRender" if self.is_interactive: dspy_callback = "dspyIPR" - display.params.SetString("dspyParams", + display.params.SetString("dspyParams", "%s -port %d -crop 1 0 1 0 -notes %s" % (dspy_callback, port, dspy_info)) cam_sg_node = self.main_camera @@ -1639,8 +1581,8 @@ def export_displays(self): cam_dspys = cams_to_dspys.get(cam_sg_node, list()) cam_dspys.append(display) - cams_to_dspys[cam_sg_node] = cam_dspys - + cams_to_dspys[cam_sg_node] = cam_dspys + for cam_sg_node,cam_dspys in cams_to_dspys.items(): #cam = self.rman_cameras.get(db_name, None) if not cam_sg_node: @@ -1649,7 +1591,7 @@ def export_displays(self): cam_sg_node.sg_camera_node.SetRenderable(2) cam_sg_node.sg_camera_node.SetDisplay(cam_dspys) - self.sg_scene.SetDisplayChannel(displaychannels) + self.sg_scene.SetDisplayChannel(displaychannels) def export_stats(self): @@ -1664,7 +1606,7 @@ def export_stats(self): integrator = bl_integrator_node.bl_label stats_mgr._integrator = integrator #stats_mgr._minSamples = rm.hider_minSamples - stats_mgr._maxSamples = rm.hider_maxSamples + stats_mgr._maxSamples = rm.hider_maxSamples def export_viewport_stats(self, integrator=''): @@ -1681,4 +1623,4 @@ def export_viewport_stats(self, integrator=''): #stats_mgr._minSamples = rm.ipr_hider_minSamples stats_mgr._maxSamples = rm.ipr_hider_maxSamples stats_mgr._decidither = rm.hider_decidither - stats_mgr._res_mult = int(self.viewport_render_res_mult*100) + stats_mgr._res_mult = int(self.viewport_render_res_mult*100) \ No newline at end of file diff --git a/rman_scene_sync.py b/rman_scene_sync.py index 58b4f5bc..dcd4cadf 100644 --- a/rman_scene_sync.py +++ b/rman_scene_sync.py @@ -1,9 +1,9 @@ # utils from .rfb_utils import object_utils -from .rfb_utils import transform_utils from .rfb_utils import texture_utils from .rfb_utils import scene_utils -from .rfb_utils import shadergraph_utils +from .rfb_utils.timer_utils import time_this +from .rfb_utils import string_utils from .rfb_logger import rfb_log from .rman_sg_nodes.rman_sg_lightfilter import RmanSgLightFilter @@ -11,6 +11,31 @@ from . import rman_constants import bpy +class RmanUpdate: + ''' + The RmanUpdate class. A helper class to indicate what kind of update + we're dealing with + + Attributes: + is_updated_geometry (bool) - Whether the geometry has been updated. This doesn't necessarily + mean the actual geometry was updated. Attribute changes are also + considered geometry updates + is_updated_transform (bool) - Whether the geometry was transformed + is_updated_shading (bool) - If the shader on the object has changed + is_updated_attributes (bool) - Wether an Ri attribute was changed + updated_prop_name (str) - The name of the Blender property that was changed, for either + is_updated_attributes or is_updated_geometry case + do_clear_instances (bool) - Whether we should clear/delete all instances of the prototype + + ''' + def __init__(self): + self.is_updated_geometry = False + self.is_updated_transform = False + self.is_updated_shading = False + self.is_updated_attributes = False + self.updated_prop_name = None + self.do_clear_instances = True + class RmanSceneSync(object): ''' The RmanSceneSync class handles keeping the RmanScene object in sync @@ -29,14 +54,11 @@ def __init__(self, rman_render=None, rman_scene=None, sg_scene=None): self.rman = rman_render.rman self.rman_scene = rman_scene self.sg_scene = sg_scene - - self.new_objects = set() # set of objects that were added to the scene - self.new_cameras = set() # set of new camera objects that were added to the scene - self.update_instances = set() # set of objects we need to update their instances - self.update_particles = set() # set of objects we need to update their particle systemd - self.do_delete = False # whether or not we need to do an object deletion - self.do_add = False # whether or not we need to add an object self.num_instances_changed = False # if the number of instances has changed since the last update + self.frame_number_changed = False + self.check_all_instances = False # force checking all instances + + self.rman_updates = dict() # A dicitonary to hold RmanUpdate instances @property def sg_scene(self): @@ -62,58 +84,70 @@ def update_view(self, context, depsgraph): else: translator.update_transform(camera, rman_sg_camera) - def _scene_updated(self): - # Check changes to local view - if self.rman_scene.bl_local_view and (self.rman_scene.context.space_data.local_view is None): - self.rman_scene.bl_local_view = False - for ob in self.rman_scene.bl_scene.objects: - if ob.type in ('ARMATURE', 'CURVE', 'CAMERA', 'LIGHT'): - continue - self.clear_instances(ob) - self.update_instances.add(ob.original) - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - self.rman_scene.check_solo_light() - elif not self.rman_scene.bl_local_view and (self.rman_scene.context.space_data.local_view is not None): - self.rman_scene.bl_local_view = True - for ob in self.rman_scene.bl_scene.objects: - if ob.type in ('ARMATURE', 'CURVE', 'CAMERA', 'LIGHT'): - continue - self.clear_instances(ob) - self.update_instances.add(ob.original) - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - self.rman_scene.check_solo_light() - - # Check view_layer - view_layer = self.rman_scene.depsgraph.view_layer - if len(view_layer.objects) != self.rman_scene.num_objects_in_viewlayer: - # objects can be removed from the viewlayer by hiding a collection. - # Figure out the difference using sets and re-emit their instances. - self.rman_scene.num_objects_in_viewlayer = len(view_layer.objects) - view_layer = self.rman_scene.depsgraph.view_layer - set1 = set(self.rman_scene.objects_in_viewlayer) - set2 = set((view_layer.objects)) - set_diff1 = set1.difference(set2) - set_diff2 = set2.difference(set1) - - objects = list(set_diff1.union(set_diff2)) - for o in list(objects): - try: - self.update_instances.add(o.original) - self.clear_instances(o) - self.update_particles.add(o) - self.update_geometry_node_instances(o) - except: - continue + def create_rman_update(self, ob_key, **kwargs): + rman_update = RmanUpdate() + rman_update.is_updated_shading = kwargs.get('update_shading', False) + rman_update.is_updated_transform = kwargs.get('update_transform', False) + rman_update.is_updated_geometry = kwargs.get('update_geometry', False) + rman_update.is_updated_attributes = kwargs.get('update_attributes', False) + rman_update.updated_prop_name = kwargs.get('prop_name', None) + rman_update.do_clear_instances = kwargs.get('clear_instances', True) + self.rman_updates[ob_key] = rman_update + return rman_update + + @time_this + def scene_updated(self): + # Check visible objects + visible_objects = self.rman_scene.context.visible_objects + if not self.num_instances_changed: + if len(visible_objects) != self.rman_scene.num_objects_in_viewlayer: + rfb_log().debug("\tNumber of visible objects changed: %d -> %d" % (self.rman_scene.num_objects_in_viewlayer, len(visible_objects))) + # The number of visible objects has changed. + # Figure out the difference using sets + set1 = set(self.rman_scene.objects_in_viewlayer) + set2 = set(visible_objects) + set_diff1 = set1.difference(set2) + set_diff2 = set2.difference(set1) + + objects = list(set_diff1.union(set_diff2)) + for o in list(objects): + try: + if o.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_shading = True + rman_update.is_updated_transform = True + self.rman_updates[o.original] = rman_update + except: + continue + #self.check_all_instances = True - self.rman_scene.objects_in_viewlayer = [o for o in view_layer.objects] + self.rman_scene.num_objects_in_viewlayer = len(visible_objects) + self.rman_scene.objects_in_viewlayer = [o for o in visible_objects] if self.rman_scene.bl_frame_current != self.rman_scene.bl_scene.frame_current: # frame changed, update any materials and objects that # are marked as frame sensitive rfb_log().debug("Frame changed: %d -> %d" % (self.rman_scene.bl_frame_current, self.rman_scene.bl_scene.frame_current)) self.rman_scene.bl_frame_current = self.rman_scene.bl_scene.frame_current - material_translator = self.rman_scene.rman_translators["MATERIAL"] + self.frame_number_changed = True + + # check for frame sensitive objects + for id in self.rman_scene.depsgraph.ids: + if isinstance(id, bpy.types.Object): + o = id.original + if o.type == 'CAMERA': + rman_sg_node = self.rman_scene.rman_cameras.get(o.original, None) + else: + rman_sg_node = self.rman_scene.get_rman_prototype(object_utils.prototype_key(o), create=False) + if rman_sg_node and rman_sg_node.is_frame_sensitive: + if o.original not in self.rman_updates: + o.original.update_tag() + elif isinstance(id, bpy.types.Material): + mat = id.original + rman_sg_material = self.rman_scene.rman_materials.get(mat, None) + if rman_sg_material and rman_sg_material.is_frame_sensitive: + mat.node_tree.update_tag() with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): # update frame number @@ -121,36 +155,17 @@ def _scene_updated(self): options.SetInteger(self.rman.Tokens.Rix.k_Ri_Frame, self.rman_scene.bl_frame_current) self.rman_scene.sg_scene.SetOptions(options) - for mat in bpy.data.materials: - db_name = object_utils.get_db_name(mat) - rman_sg_material = self.rman_scene.rman_materials.get(mat.original, None) - if rman_sg_material and rman_sg_material.is_frame_sensitive: - material_translator.update(mat, rman_sg_material) - - for o in bpy.data.objects: - rman_type = object_utils._detect_primitive_(o) - rman_sg_node = self.rman_scene.rman_objects.get(o.original, None) - if not rman_sg_node: - continue - translator = self.rman_scene.rman_translators.get(rman_type, None) - if translator and rman_sg_node.is_frame_sensitive: - translator.update(o, rman_sg_node) - def _mesh_light_update(self, mat): + object_list = list() with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - for ob_inst in self.rman_scene.depsgraph.object_instances: - psys = None - if ob_inst.is_instance: - ob = ob_inst.instance_object - group_db_name = object_utils.get_group_db_name(ob_inst) - else: - ob = ob_inst.object - group_db_name = object_utils.get_group_db_name(ob_inst) + for ob_inst in self.rman_scene.depsgraph.object_instances: + ob = ob_inst.object.evaluated_get(self.rman_scene.depsgraph) if not hasattr(ob.data, 'materials'): continue if ob.type in ('ARMATURE', 'CURVE', 'CAMERA'): - continue - rman_sg_node = self.rman_scene.rman_objects.get(ob.original, None) + continue + proto_key = object_utils.prototype_key(ob_inst) + rman_sg_node = self.rman_scene.get_rman_prototype(proto_key) if rman_sg_node: found = False for name, material in ob.data.materials.items(): @@ -158,23 +173,27 @@ def _mesh_light_update(self, mat): found = True if found: - rman_sg_group = rman_sg_node.instances.get(group_db_name, None) - if rman_sg_group: - rman_sg_node.instances.pop(group_db_name) - self.rman_scene.sg_scene.DeleteDagNode(rman_sg_group.sg_node) - self.rman_scene._export_instance(ob_inst) - - def _material_updated(self, obj): - mat = obj.id - rman_sg_material = self.rman_scene.rman_materials.get(mat.original, None) + del self.rman_scene.rman_prototypes[proto_key] + if ob not in object_list: + object_list.append(ob) + + for ob in object_list: + ob.update_tag() + + def material_updated(self, ob_update, rman_sg_material=None): + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + mat = ob_update.id + else: + mat = ob_update + if rman_sg_material is None: + rman_sg_material = self.rman_scene.rman_materials.get(mat.original, None) translator = self.rman_scene.rman_translators["MATERIAL"] db_name = object_utils.get_db_name(mat) if not rman_sg_material: # Double check if we can't find the material because of an undo rman_sg_material = self.update_materials_dict(mat) - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - mat = obj.id + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): if not rman_sg_material: rfb_log().debug("New material: %s" % mat.name) db_name = object_utils.get_db_name(mat) @@ -187,324 +206,165 @@ def _material_updated(self, obj): # update db_name rman_sg_material.db_name = db_name - def _light_filter_transform_updated(self, obj): - ob = obj.id - rman_sg_lightfilter = self.rman_scene.rman_objects.get(ob.original, None) - if rman_sg_lightfilter: - rman_group_translator = self.rman_scene.rman_translators['GROUP'] - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - rman_group_translator.update_transform(ob, rman_sg_lightfilter) - - def _gpencil_transform_updated(self, obj): - ob = obj.id - rman_sg_gpencil = self.rman_scene.rman_objects.get(ob.original, None) - if rman_sg_gpencil: - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - rman_group_translator = self.rman_scene.rman_translators['GROUP'] - for ob_inst in self.rman_scene.depsgraph.object_instances: - group_db_name = object_utils.get_group_db_name(ob_inst) - rman_sg_group = rman_sg_gpencil.instances.get(group_db_name, None) - if rman_sg_group: - rman_group_translator.update_transform(ob, rman_sg_group) - - def _obj_geometry_updated(self, obj): - ob = obj.id - rman_type = object_utils._detect_primitive_(ob) - db_name = object_utils.get_db_name(ob, rman_type=rman_type) - rman_sg_node = self.rman_scene.rman_objects.get(ob.original, None) - - if rman_type in ['LIGHT', 'LIGHTFILTER', 'CAMERA']: - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - if rman_type == 'LIGHTFILTER': - self.rman_scene.rman_translators['LIGHTFILTER'].update(ob, rman_sg_node) - for light_ob in rman_sg_node.lights_list: - if isinstance(light_ob, bpy.types.Material): - rman_sg_material = self.rman_scene.rman_materials.get(light_ob.original, None) - if rman_sg_material: - self.rman_scene.rman_translators['MATERIAL'].update_light_filters(light_ob, rman_sg_material) - else: - rman_sg_light = self.rman_scene.rman_objects.get(light_ob.original, None) - if rman_sg_light: - self.rman_scene.rman_translators['LIGHT'].update_light_filters(light_ob, rman_sg_light) - - elif rman_type == 'LIGHT': - self.rman_scene.rman_translators['LIGHT'].update(ob, rman_sg_node) - - if not self.rman_scene.scene_solo_light: - # only set if a solo light hasn't been set - if not self.rman_scene.check_light_local_view(ob, rman_sg_node): - rman_sg_node.sg_node.SetHidden(ob.data.renderman.mute) - elif rman_type == 'CAMERA': - ob = ob.original - rman_camera_translator = self.rman_scene.rman_translators['CAMERA'] - if not self.rman_scene.is_viewport_render: - rman_camera_translator.update(ob, rman_sg_node) - else: - rman_camera_translator.update_viewport_cam(ob, rman_sg_node, force_update=True) - + def light_filter_transform_updated(self, ob, rman_sg_lightfilter): + rman_group_translator = self.rman_scene.rman_translators['GROUP'] + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + rman_group_translator.update_transform(ob, rman_sg_lightfilter) + + def light_filter_updated(self, ob_update, force_update=False): + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + ob = ob_update.id.evaluated_get(self.rman_scene.depsgraph) + else: + ob = ob_update.evaluated_get(self.rman_scene.depsgraph) + + proto_key = object_utils.prototype_key(ob) + rman_sg_lightfilter = self.rman_scene.get_rman_prototype(proto_key) + if not rman_sg_lightfilter: + # Light filter needs to be added + rman_update = RmanUpdate() + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + rman_update.is_updated_geometry = ob_update.is_updated_geometry + rman_update.is_updated_shading = ob_update.is_updated_shading + rman_update.is_updated_transform = ob_update.is_updated_transform + else: + rman_update.is_updated_geometry = True + rman_update.is_updated_shading = True + rman_update.is_updated_transform = True + self.rman_updates[ob.original] = rman_update + return + if force_update or ob_update.is_updated_transform or ob_update.is_updated_shading: + rfb_log().debug("\tLight Filter: %s Transform Updated" % ob.name) + self.light_filter_transform_updated(ob, rman_sg_lightfilter) + if force_update or ob_update.is_updated_geometry: + rfb_log().debug("\tLight Filter: %s Shading Updated" % ob.name) + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + self.rman_scene.rman_translators['LIGHTFILTER'].update(ob, rman_sg_lightfilter) + for light_ob in rman_sg_lightfilter.lights_list: + if isinstance(light_ob, bpy.types.Material): + self.material_updated(light_ob) + elif light_ob.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_geometry = True + rman_update.is_updated_transform = True + self.rman_updates[light_ob.original] = rman_update + + def camera_updated(self, ob_update, force_update=False): + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + ob = ob_update.id.evaluated_get(self.rman_scene.depsgraph) else: - if rman_sg_node.rman_type != rman_type: - # for now, we don't allow the rman_type to be changed - rfb_log().error("Changing primitive type is currently not supported.") + ob = ob_update + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + rman_sg_camera = self.rman_scene.rman_cameras.get(ob.original) + translator = self.rman_scene.rman_translators['CAMERA'] + + rman_update = RmanUpdate() + self.rman_updates[ob.original] = rman_update + + if not rman_sg_camera: + rfb_log().debug("\tNew Camera: %s" % ob.name) + db_name = object_utils.get_db_name(ob) + rman_sg_camera = translator._export_render_cam(ob, db_name) + self.rman_scene.rman_cameras[ob.original] = rman_sg_camera + + self.rman_scene.sg_scene.Root().AddChild(rman_sg_camera.sg_node) + self.rman_scene.sg_scene.Root().AddCoordinateSystem(rman_sg_camera.sg_node) return - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - translator = self.rman_scene.rman_translators.get(rman_type, None) - if not translator: - return - translator.update(ob, rman_sg_node) - translator.export_object_primvars(ob, rman_sg_node) - # material slots could have changed, so we need to double - # check that too - for k,v in rman_sg_node.instances.items(): - self.rman_scene.attach_material(ob, v) - - if rman_sg_node.sg_node: - if not ob.show_instancer_for_viewport: - rman_sg_node.sg_node.SetHidden(1) - else: - rman_sg_node.sg_node.SetHidden(-1) - - def update_light_visibility(self, rman_sg_node, ob): - if not self.rman_scene.scene_solo_light: - vis = rman_sg_node.sg_node.GetHidden() - if vis == -1: - vis = 0 - result = False - update_instances = False - # if vis is inherit, and none of the other visibility attrs are set to hide - if vis == -1 and not ob.hide_get() and int(ob.renderman.mute) == 0: - update_instances = True - result = False - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - if self.rman_scene.check_light_local_view(ob, rman_sg_node): - update_instances = True - result = True - elif not ob.hide_get(): - rman_sg_node.sg_node.SetHidden(ob.renderman.mute) - update_instances = True - result = (vis != int(ob.renderman.mute)) + + if force_update or ob_update.is_updated_geometry: + rfb_log().debug("\tUpdated Camera: %s" % ob.name) + if not self.rman_scene.is_viewport_render: + translator.update(ob, rman_sg_camera) else: - rman_sg_node.sg_node.SetHidden(1) - result = (vis != 1) - - if update_instances and len(rman_sg_node.instances) < 1: - self.update_instances.add(ob.original) - return result - - def update_object_visibility(self, rman_sg_node, ob): - ob_data = ob - rman_type = object_utils._detect_primitive_(ob_data) - particle_systems = getattr(ob_data, 'particle_systems', list()) - has_particle_systems = len(particle_systems) > 0 - is_hidden = ob_data.hide_get() - - # double check hidden value - if rman_type in ['LIGHT']: - if self.update_light_visibility(rman_sg_node, ob): - rfb_log().debug("Update light visibility: %s" % ob.name) - return True - else: - if rman_sg_node.is_hidden != is_hidden: - self.do_delete = False - rman_sg_node.is_hidden = is_hidden - if rman_type == 'EMPTY': - self.update_empty(ob, rman_sg_node) - else: - self.update_instances.add(ob.original) - self.clear_instances(ob, rman_sg_node) - if has_particle_systems: - self.update_particles.add(ob.original) - return True - return False - - def update_particle_settings(self, obj, particle_settings_node): - rfb_log().debug("Check %s for particle settings." % obj.id.name) - # A ParticleSettings node was updated. Try to look for it. - ob = obj.id - rman_type = object_utils._detect_primitive_(ob) - for psys in obj.id.particle_systems: - if psys.settings.original == particle_settings_node: - if psys.settings.type == 'FLIP' and rman_type == 'FLUID': - fluid_translator = self.rman_scene.rman_translators['FLUID'] - rman_sg_node = self.rman_scene.rman_objects.get(ob.original, None) - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - fluid_translator.update(ob, rman_sg_node) - return + translator.update_viewport_cam(ob, rman_sg_camera, force_update=True) - ob_psys = self.rman_scene.rman_particles.get(obj.id.original, dict()) - rman_sg_particles = ob_psys.get(psys.settings.original, None) - if rman_sg_particles: - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - psys_translator = self.rman_scene.rman_translators['PARTICLES'] - psys_translator.update(obj.id, psys, rman_sg_particles) + if force_update or ob_update.is_updated_transform: + # we deal with main camera transforms in view_draw + if self.rman_scene.is_viewport_render and self.rman_scene.main_camera == rman_sg_camera: return - # This is a particle instancer. The instanced object needs to updated - elif object_utils.is_particle_instancer(psys): - inst_object = getattr(particle_settings_node, 'instance_object', None) - collection = getattr(particle_settings_node, 'instance_collection', None) - if inst_object: - self.update_instances.add(inst_object.original) - if collection: - for col_obj in collection.all_objects: - if col_obj.original not in self.rman_scene.rman_objects: - self.new_objects.add(col_obj.original) - self.update_instances.add(col_obj.original) - break - - # Update any other instance objects this object instanced. The instanced - # object may have changed - rman_sg_node = self.rman_scene.rman_objects.get(obj.id.original, None) - for instance_obj in rman_sg_node.objects_instanced: - self.clear_instances(instance_obj) - self.update_instances.add(instance_obj) - - def update_particle_systems(self): - - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - for ob in self.update_particles: - rman_type = object_utils._detect_primitive_(ob) - if rman_type not in ['MESH', 'POINTS']: + if translator._update_render_cam_transform(ob, rman_sg_camera): + rfb_log().debug("\tCamera Transform Updated: %s" % ob.name) + + + def check_particle_instancer(self, ob_update, psys): + # this particle system is a instancer + inst_ob = getattr(psys.settings, 'instance_object', None) + collection = getattr(psys.settings, 'instance_collection', None) + if inst_ob: + if inst_ob.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_shading = ob_update.is_updated_shading + rman_update.is_updated_transform = ob_update.is_updated_transform + self.rman_updates[inst_ob.original] = rman_update + elif collection: + for col_obj in collection.all_objects: + if not col_obj.original.data: continue - rman_sg_node = self.rman_scene.rman_objects.get(ob.original, None) - if rman_sg_node is None: + if col_obj.original in self.rman_updates: continue - ob_eval = ob.evaluated_get(self.rman_scene.depsgraph) - rfb_log().debug("Update particle systems for: %s" % ob.name) - - # any objects that this object instanced, need to update their instances - for instance_obj in rman_sg_node.objects_instanced: - self.clear_instances(instance_obj) - self.update_instances.add(instance_obj) - - if rman_sg_node.rman_sg_particle_group_node: - rman_sg_node.rman_sg_particle_group_node.sg_node.RemoveAllChildren() - - if len(ob_eval.particle_systems) < 1: - continue - - if not rman_sg_node.rman_sg_particle_group_node: - db_name = rman_sg_node.db_name - particles_group_db = '' - rman_sg_node.rman_sg_particle_group_node = self.rman_scene.rman_translators['GROUP'].export(None, particles_group_db) - rman_sg_node.sg_node.AddChild(rman_sg_node.rman_sg_particle_group_node.sg_node) - - psys_translator = self.rman_scene.rman_translators['PARTICLES'] - - for psys in ob_eval.particle_systems: - if object_utils.is_particle_instancer(psys): - # this particle system is a instancer, add the instanced object - # to the self.update_instances list - inst_ob = getattr(psys.settings, 'instance_object', None) - collection = getattr(psys.settings, 'instance_collection', None) - if inst_ob: - self.update_instances.add(inst_ob.original) - rman_instance_sg_node = self.rman_scene.rman_objects.get(inst_ob.original, None) - if rman_instance_sg_node: - self.clear_instances(inst_ob.original, rman_instance_sg_node) - elif collection: - for col_obj in collection.all_objects: - self.update_instances.add(col_obj.original) - rman_instance_sg_node = self.rman_scene.rman_objects.get(col_obj.original, None) - if rman_instance_sg_node: - self.clear_instances(col_obj.original, rman_instance_sg_node) - else: - self.new_objects.add(col_obj.original) - continue - - ob_psys = self.rman_scene.rman_particles.get(ob_eval.original, dict()) - rman_sg_particles = ob_psys.get(psys.settings.original, None) - if not rman_sg_particles: - psys_db_name = '%s' % psys.name - rman_sg_particles = psys_translator.export(ob, psys, psys_db_name) - if not rman_sg_particles: - continue - psys_translator.update(ob, psys, rman_sg_particles) - ob_psys[psys.settings.original] = rman_sg_particles - self.rman_scene.rman_particles[ob.original] = ob_psys - rman_sg_node.rman_sg_particle_group_node.sg_node.AddChild(rman_sg_particles.sg_node) - - def update_empty(self, ob, rman_sg_node=None): + rman_update = RmanUpdate() + rman_update.is_updated_shading = ob_update.is_updated_shading + rman_update.is_updated_transform = ob_update.is_updated_transform + self.rman_updates[col_obj.original] = rman_update + + def update_particle_emitter(self, ob, psys, delete=False): + psys_translator = self.rman_scene.rman_translators['PARTICLES'] + proto_key = object_utils.prototype_key(ob) + rman_sg_particles = self.rman_scene.get_rman_particles(proto_key, psys, ob, create=not delete) + if rman_sg_particles: + psys_translator.update(ob, psys, rman_sg_particles) + + def update_particle_emitters(self, ob): + for psys in ob.particle_systems: + if not object_utils.is_particle_instancer(psys): + self.update_particle_emitter(ob, psys) + + def update_empty(self, ob_update, rman_sg_node=None): + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + ob = ob_update.id + else: + ob = ob_update.evaluated_get(self.rman_scene.depsgraph) rfb_log().debug("Update empty: %s" % ob.name) - if ob.is_instancer: + if ob.is_instancer: + rfb_log().debug("\tEmpty is an instancer") collection = ob.instance_collection if collection: - if self.num_instances_changed: - for col_obj in collection.all_objects: - self.update_instances.add(col_obj.original) - rman_instance_sg_node = self.rman_scene.rman_objects.get(col_obj.original, None) - if rman_instance_sg_node: - self.clear_instances(col_obj.original, rman_instance_sg_node) - else: - self.new_objects.add(col_obj.original) - self.update_particles.add(col_obj) - else: - for col_obj in collection.all_objects: - self.update_instances.add(col_obj.original) - self.update_particles.add(col_obj) - + for col_obj in collection.all_objects: + if not col_obj.original.data: + continue + if col_obj.original in self.rman_updates: + continue + rman_update = RmanUpdate() + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + rman_update.is_updated_geometry = ob_update.is_updated_geometry + rman_update.is_updated_shading = ob_update.is_updated_shading + rman_update.is_updated_transform = ob_update.is_updated_transform + else: + rman_update.is_updated_geometry = True + rman_update.is_updated_shading = True + rman_update.is_updated_transform = True + self.rman_updates[col_obj.original] = rman_update else: - translator = self.rman_scene.rman_translators['EMPTY'] + rfb_log().debug("\tRegular empty") + proto_key = object_utils.prototype_key(ob) + rman_sg_node = self.rman_scene.get_rman_prototype(proto_key) + if not rman_sg_node: + return + translator = self.rman_scene.rman_translators['EMPTY'] with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): translator.export_transform(ob, rman_sg_node.sg_node) + translator.export_object_attributes(ob, rman_sg_node) + self.rman_scene.attach_material(ob, rman_sg_node) if ob.renderman.export_as_coordsys: self.rman_scene.get_root_sg_node().AddCoordinateSystem(rman_sg_node.sg_node) else: - self.rman_scene.get_root_sg_node().RemoveCoordinateSystem(rman_sg_node.sg_node) - - def reemit_instances(self): - # update instances - if not self.update_instances: - return - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - # Re-emit instances for all objects in self.update_instances - rfb_log().debug("Re-emit instances") - rman_group_translator = self.rman_scene.rman_translators['GROUP'] - for ob_inst in self.rman_scene.depsgraph.object_instances: - parent = None - if ob_inst.is_instance: - ob = ob_inst.instance_object - parent = ob_inst.parent - else: - ob = ob_inst.object - - if ob.original not in self.update_instances: - continue - - rman_type = object_utils._detect_primitive_(ob) - rman_sg_node = self.rman_scene.rman_objects.get(ob.original, None) - if rman_sg_node: - translator = self.rman_scene.rman_translators.get(rman_type, None) - translator.export_object_primvars(ob, rman_sg_node) - - group_db_name = object_utils.get_group_db_name(ob_inst) - rman_sg_group = rman_sg_node.instances.get(group_db_name, None) - if rman_sg_group: - rman_group_translator.update_transform(ob_inst, rman_sg_group) - # object attrs - rman_group_translator.export_object_attributes(ob, rman_sg_group) - if rman_sg_group.bl_psys_settings: - self.rman_scene.attach_particle_material(rman_sg_group.bl_psys_settings, parent, ob, rman_sg_group) - else: - self.rman_scene.attach_material(ob, rman_sg_group) - continue - - self.rman_scene._export_instance(ob_inst) - - def clear_instances(self, ob, rman_sg_node=None): - rfb_log().debug("Deleting instances") - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - if not rman_sg_node: - rman_sg_node = self.rman_scene.rman_objects.get(ob.original) - for k,rman_sg_group in rman_sg_node.instances.items(): - if ob.parent and object_utils._detect_primitive_(ob.parent) == 'EMPTY': - rman_empty_node = self.rman_scene.rman_objects.get(ob.parent.original) - rman_empty_node.sg_node.RemoveChild(rman_sg_group.sg_node) - else: - self.rman_scene.get_root_sg_node().RemoveChild(rman_sg_group.sg_node) - rman_sg_node.instances.clear() - + self.rman_scene.get_root_sg_node().RemoveCoordinateSystem(rman_sg_node.sg_node) + def update_materials_dict(self, mat): - # See comment below in update_objects_dict + # Try to see if we already have a material with the same db_name + # We need to do this because undo/redo causes all bpy.types.ID + # references to be invalidated (see: https://docs.blender.org/api/current/info_gotcha.html) + # We don't want to accidentally mistake this for a new object, so we need to update + # our materials dictionary with the new bpy.types.ID reference rman_sg_material = None for id, rman_sg_node in self.rman_scene.rman_materials.items(): if rman_sg_node: @@ -517,139 +377,295 @@ def update_materials_dict(self, mat): return rman_sg_material - def update_objects_dict(self, ob, rman_type=None): - # Try to see if we already have an obj with the same db_name - # We need to do this because undo/redo causes all bpy.types.ID - # references to be invalidated (see: https://docs.blender.org/api/current/info_gotcha.html) - # We don't want to accidentally mistake this for a new object, so we need to update - # our objects dictionary with the new bpy.types.ID reference - rman_sg_node = None - for id, rsn in self.rman_scene.rman_objects.items(): - if rsn: - db_name = object_utils.get_db_name(ob, rman_type=rman_type) - if rsn.db_name == db_name: - self.rman_scene.rman_objects[ob.original] = rsn - del self.rman_scene.rman_objects[id] - if id in self.rman_scene.rman_cameras: - self.rman_scene.rman_cameras[ob.original] = rsn - del self.rman_scene.rman_cameras[id] - rman_sg_node = rsn - break - return rman_sg_node - def update_collection(self, coll): # mark all objects in a collection # as needing their instances updated - # the collection could have been updated with new objects - # FIXME: like grease pencil above we seem to crash when removing and adding instances - # of curves, we need to figure out what's going on for o in coll.all_objects: - if o.type in ('ARMATURE', 'CURVE', 'CAMERA'): + if o.type in ('ARMATURE', 'CAMERA'): + continue + if o.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_shading = True + rman_update.is_updated_transform = True + self.rman_updates[o.original] = rman_update + + def check_empty_instancer(self, dps_update): + ob = dps_update.id.evaluated_get(self.rman_scene.depsgraph) + coll = ob.instance_collection + rfb_log().debug("\tEmpty Instancer %s Updated" % ob.name) + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + proto_key = object_utils.prototype_key(ob) + rman_sg_node = self.rman_scene.get_rman_prototype(proto_key, ob=ob, create=True) + rman_type = object_utils._detect_primitive_(ob) + self.rman_scene.rman_translators[rman_type].clear_children(rman_sg_node) + + # mark all objects in the instance collection + # as needing their instances updated + for o in coll.all_objects: + if o.type in ('ARMATURE', 'CAMERA'): continue + if o.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_geometry = dps_update.is_updated_geometry + rman_update.is_updated_shading = dps_update.is_updated_shading + rman_update.is_updated_transform = dps_update.is_updated_transform + self.rman_updates[o.original] = rman_update + + def update_portals(self, ob): + for portal in scene_utils.get_all_portals(ob): + portal.original.update_tag() + def check_light_datablock(self, ob_update): + if isinstance(ob_update, bpy.types.DepsgraphUpdate): + ob = ob_update.id.original + else: + ob = ob_update.original + users = self.rman_scene.context.blend_data.user_map(subset={ob}, value_types={'OBJECT'}) + for o in users[ob]: rman_type = object_utils._detect_primitive_(o) - rman_sg_node = self.rman_scene.rman_objects.get(o.original, None) - if not rman_sg_node: - if not self.update_objects_dict(o, rman_type=rman_type): - self.new_objects.add(o) - self.update_instances.add(o) - continue + if rman_type == 'LIGHTFILTER': + self.light_filter_updated(o, force_update=True) + elif o.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_geometry = True + rman_update.is_updated_shading = True + self.rman_updates[o.original] = rman_update + + def check_focus_object(self, ob): + for camera in bpy.data.cameras: + rm = camera.renderman + if rm.rman_focus_object and rm.rman_focus_object.original == ob.original: + users = self.rman_scene.context.blend_data.user_map(subset={camera}) + for o in users[camera]: + self.camera_updated(o.original, force_update=True) + + def check_object_datablock(self, dps_update): + ob_eval = dps_update.id.evaluated_get(self.rman_scene.depsgraph) + rman_type = object_utils._detect_primitive_(ob_eval) + + if ob_eval.type in ('ARMATURE'): + return + + # These types need special handling + if rman_type == 'EMPTY': + self.update_empty(dps_update) + elif rman_type == 'LIGHTFILTER': + self.light_filter_updated(dps_update) + elif rman_type == 'CAMERA': + self.camera_updated(dps_update) + elif rman_type == 'EMPTY_INSTANCER': + self.check_empty_instancer(dps_update) + else: + if dps_update.id.original not in self.rman_updates: + rfb_log().debug("\tObject: %s Updated" % dps_update.id.name) + rfb_log().debug("\t is_updated_geometry: %s" % str(dps_update.is_updated_geometry)) + rfb_log().debug("\t is_updated_shading: %s" % str(dps_update.is_updated_shading)) + rfb_log().debug("\t is_updated_transform: %s" % str(dps_update.is_updated_transform)) + rman_update = RmanUpdate() + rman_update.is_updated_geometry = dps_update.is_updated_geometry + rman_update.is_updated_shading = dps_update.is_updated_shading + rman_update.is_updated_transform = dps_update.is_updated_transform + if not rman_update.is_updated_geometry: + rman_update.do_clear_instances = False + self.rman_updates[dps_update.id.original] = rman_update + + for psys in ob_eval.particle_systems: + if object_utils.is_particle_instancer(psys): + self.check_particle_instancer(dps_update, psys) + + # Check if this object is the focus object the camera. If it is + # we need to update the camera + if dps_update.is_updated_transform: + self.check_focus_object(ob_eval) + + def check_particle_settings(self, dps_update): + rfb_log().debug("ParticleSettings updated: %s" % dps_update.id.name) - if rman_type == 'LIGHT': - # Check light visibility. Light visibility is already handled elsewhere - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - if self.rman_scene.check_light_local_view(o, rman_sg_node): - continue + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + users = self.rman_scene.context.blend_data.user_map(subset={dps_update.id.original}, value_types={'OBJECT'}) + for o in users[dps_update.id.original]: + ob = o.evaluated_get(self.rman_scene.depsgraph) + psys = None + for ps in ob.particle_systems: + if ps.settings.original == dps_update.id.original: + psys = ps + break + if not psys: + continue + if object_utils.is_particle_instancer(psys): + # if this particle system was changed to an instancer + # make sure any old emitters/hair is removed + self.update_particle_emitter(ob, psys, delete=True) + else: + self.update_particle_emitter(ob, psys) - self.update_instances.add(o.original) - self.update_particles.add(o) - self.update_geometry_node_instances(o) - - def update_geometry_node_instances(self, obj): - def update_geo_instances(nodes): - # look for all point instance nodes - for n in [node for node in nodes if isinstance(node, bpy.types.GeometryNodePointInstance)]: - if n.instance_type == 'OBJECT': - instance_obj = n.inputs['Object'].default_value - if instance_obj: - self.clear_instances(instance_obj) - self.update_particles.add(instance_obj) - self.update_instances.add(instance_obj.original) - elif n.instance_type == 'COLLECTION': - instance_coll = n.inputs['Collection'].default_value - if instance_coll: - self.update_collection(instance_coll) - - - if rman_constants.BLENDER_VERSION_MAJOR >= 2 and rman_constants.BLENDER_VERSION_MINOR >= 92: - if isinstance(obj, bpy.types.GeometryNodeTree): - rfb_log().debug("Geometry Node Tree updated: %s" % obj.name) - # look for all point instance nodes - update_geo_instances(obj.nodes) - elif hasattr(obj, 'modifiers'): - # This is an object with modifiers. Look for any geometry node trees attached. - node_tree = None - for modifier in obj.modifiers: - if modifier.type == 'NODES': - rfb_log().debug("Geometry Node Tree updated: %s" % modifier.node_group.name) - update_geo_instances(modifier.node_group.nodes) + if o.original not in self.rman_updates: + rman_update = RmanUpdate() + rman_update.is_updated_shading = dps_update.is_updated_shading + rman_update.is_updated_transform = dps_update.is_updated_transform + self.rman_updates[o.original] = rman_update - def update_portals(self, ob): - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - translator = self.rman_scene.rman_translators['LIGHT'] - for portal in scene_utils.get_all_portals(ob): - rman_sg_node = self.rman_scene.rman_objects.get(portal.original, None) - if rman_sg_node: - translator.update(portal, rman_sg_node) + def check_shader_nodetree(self, dps_update): + if dps_update.id.name in bpy.data.node_groups: + if len(dps_update.id.nodes) < 1: + return + if not dps_update.id.name.startswith(rman_constants.RMAN_FAKE_NODEGROUP): + return + # this is one of our fake node groups with ramps + # update all of the users of this node tree + rfb_log().debug("ShaderNodeTree updated: %s" % dps_update.id.name) + users = self.rman_scene.context.blend_data.user_map(subset={dps_update.id.original}) + for o in users[dps_update.id.original]: + if self.rman_scene.is_interactive: + if hasattr(o, 'rman_nodetree'): + o.rman_nodetree.update_tag() + elif isinstance(o, bpy.types.Material): + self.update_material(o) + elif isinstance(o, bpy.types.Light): + self.check_light_datablock(o) + elif hasattr(o, 'node_tree'): + o.node_tree.update_tag() + else: + if isinstance(o, bpy.types.Light): + self.check_light_datablock(o) + elif isinstance(o, bpy.types.Material): + rman_update = RmanUpdate() + self.rman_updates[o.original] = rman_update + @time_this + def batch_update_scene(self, context, depsgraph): + self.rman_scene.bl_frame_current = self.rman_scene.bl_scene.frame_current - def update_scene(self, context, depsgraph): - ## FIXME: this function is waaayyy too big and is doing too much stuff + self.rman_updates = dict() + self.num_instances_changed = False + self.check_all_instances = False - self.new_objects.clear() - self.new_cameras.clear() - self.update_instances.clear() - self.update_particles.clear() + self.rman_scene.bl_scene = depsgraph.scene_eval + self.rman_scene.context = context - self.do_delete = False # whether or not we need to do an object deletion - self.do_add = False # whether or not we need to add an object - self.num_instances_changed = False # if the number of instances has changed since the last update + # update the frame number + options = self.rman_scene.sg_scene.GetOptions() + options.SetInteger(self.rman.Tokens.Rix.k_Ri_Frame, self.rman_scene.bl_frame_current) + self.rman_scene.sg_scene.SetOptions(options) + + # Check the number of instances. If we differ, an object may have been + # added or deleted + if self.rman_scene.num_object_instances != len(depsgraph.object_instances): + rfb_log().debug("\tNumber of instances changed: %d -> %d" % (self.rman_scene.num_object_instances, len(depsgraph.object_instances))) + self.num_instances_changed = True + self.rman_scene.num_object_instances = len(depsgraph.object_instances) + + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + for dps_update in reversed(depsgraph.updates): + if isinstance(dps_update.id, bpy.types.ParticleSettings): + self.check_particle_settings(dps_update) + + elif isinstance(dps_update.id, bpy.types.Light): + self.check_light_datablock(dps_update) + + elif isinstance(dps_update.id, bpy.types.Material): + rfb_log().debug("Material updated: %s" % dps_update.id.name) + ob = dps_update.id + if ob.original not in self.rman_updates: + rman_update = RmanUpdate() + self.rman_updates[ob.original] = rman_update + + elif isinstance(dps_update.id, bpy.types.ShaderNodeTree): + self.check_shader_nodetree(dps_update) + + elif isinstance(dps_update.id, bpy.types.Object): + self.check_object_datablock(dps_update) + elif isinstance(dps_update.id, bpy.types.GeometryNodeTree): + # create an empty RmanUpdate + self.create_rman_update(dps_update.id.original, clear_instances=False) + + if not self.rman_updates and self.num_instances_changed: + # The number of instances changed, but we are not able + # to determine what changed. We are forced to check + # all instances. + rfb_log().debug("Set check_all_instances to True") + self.check_all_instances = True + + self.check_instances(batch_mode=True) + + # update any materials + for id, rman_sg_material in self.rman_scene.rman_materials.items(): + if rman_sg_material.is_frame_sensitive or id.original in self.rman_updates: + mat = id.evaluated_get(self.rman_scene.depsgraph) + self.material_updated(mat, rman_sg_material) + + self.rman_scene.export_integrator() + self.rman_scene.export_samplefilters() + self.rman_scene.export_displayfilters() + self.rman_scene.export_displays() + + if self.rman_scene.do_motion_blur and self.rman_scene.moving_objects: + self.rman_scene.export_instances_motion() + + @time_this + def update_scene(self, context, depsgraph): + + #self.rman_updates = dict() + self.num_instances_changed = False + self.frame_number_changed = False + self.check_all_instances = False self.rman_scene.depsgraph = depsgraph self.rman_scene.bl_scene = depsgraph.scene - self.rman_scene.context = context + self.rman_scene.context = context + + if len(depsgraph.updates) < 1 and depsgraph.id_type_updated('NODETREE'): + # Updates is empty?! This seems like a Blender bug. + # We seem to get into this situation when ramps are being edited, but the scene + # has not been saved since the ramp was added. + space = getattr(bpy.context, 'space_data', None) + rfb_log().debug("------Start update scene--------") + rfb_log().debug("DepsgraphUpdates is empty. Assume this is a material edit.") + node_tree = None + if space and space.type == 'NODE_EDITOR': + node_tree = space.node_tree + if isinstance(node_tree, bpy.types.ShaderNodeTree): + node_tree.update_tag() + else: + node_tree = None + if node_tree is None and context.view_layer: + # The current space doesn't seem to be the shader editor. + # Fallback to looking for the active object + ob = context.view_layer.objects.active + if ob: + if hasattr(ob, 'active_material') and ob.active_material: + ob.active_material.node_tree.update_tag() + elif hasattr(ob, 'rman_nodetree'): + ob.rman_nodetree.update_tag() + elif ob.type == 'LIGHT': + ob.data.node_tree.update_tag() - particle_settings_node = None - prev_num_instances = self.rman_scene.num_object_instances # the number of instances previously - + rfb_log().debug("------End update scene----------") + + rfb_log().debug("------Start update scene--------") + # Check the number of instances. If we differ, an object may have been # added or deleted if self.rman_scene.num_object_instances != len(depsgraph.object_instances): + rfb_log().debug("\tNumber of instances changed: %d -> %d" % (self.rman_scene.num_object_instances, len(depsgraph.object_instances))) self.num_instances_changed = True - if self.rman_scene.num_object_instances > len(depsgraph.object_instances): - self.do_delete = True - else: - self.do_add = True self.rman_scene.num_object_instances = len(depsgraph.object_instances) - rfb_log().debug("------Start update scene--------") - for obj in reversed(depsgraph.updates): - ob = obj.id - - if isinstance(obj.id, bpy.types.Scene): - self._scene_updated() + for dps_update in reversed(depsgraph.updates): + if isinstance(dps_update.id, bpy.types.Scene): + self.scene_updated() - elif isinstance(obj.id, bpy.types.World): + elif isinstance(dps_update.id, bpy.types.World): with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): self.rman_scene.export_integrator() self.rman_scene.export_samplefilters() self.rman_scene.export_displayfilters() self.rman_scene.export_viewport_stats() - elif isinstance(obj.id, bpy.types.Camera): - rfb_log().debug("Camera updated: %s" % obj.id.name) + elif isinstance(dps_update.id, bpy.types.Camera): + rfb_log().debug("Camera updated: %s" % dps_update.id.name) if self.rman_scene.is_viewport_render: - if self.rman_scene.bl_scene.camera.data != obj.id: + if self.rman_scene.bl_scene.camera.data != dps_update.id: continue rman_sg_camera = self.rman_scene.main_camera translator = self.rman_scene.rman_translators['CAMERA'] @@ -659,287 +675,291 @@ def update_scene(self, context, depsgraph): translator = self.rman_scene.rman_translators['CAMERA'] with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): for ob, rman_sg_camera in self.rman_scene.rman_cameras.items(): - if ob.original.name != obj.id.name: + if ob.original.name != dps_update.id.name: continue translator._update_render_cam(ob.original, rman_sg_camera) - elif isinstance(obj.id, bpy.types.Material): - rfb_log().debug("Material updated: %s" % obj.id.name) - self._material_updated(obj) - - elif isinstance(obj.id, bpy.types.Mesh): - rfb_log().debug("Mesh updated: %s" % obj.id.name) - ''' - # Experimental code path. We can use context.blend_data.user_map to ask - # what objects use this mesh. We can then loop thru and call object_update on these - # objects. - # We could also try doing the same thing when we add a new Material. i.e.: - # use user_map to figure out what objects are using this material; however, that would require - # two loops thru user_map - users = context.blend_data.user_map(subset={obj.id.original}, value_types={'OBJECT'}) - translator = self.rman_scene.rman_translators['MESH'] - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - for o in users[obj.id.original]: - rman_type = object_utils._detect_primitive_(o) - if rman_type != 'MESH': - continue - rman_sg_node = self.rman_scene.rman_objects.get(o.original, None) - translator.update(o, rman_sg_node) - translator.export_object_primvars(o, rman_sg_node) - # material slots could have changed, so we need to double - # check that too - for k,v in rman_sg_node.instances.items(): - self.rman_scene.attach_material(o, v) - return - ''' - - elif isinstance(obj.id, bpy.types.ParticleSettings): - rfb_log().debug("ParticleSettings updated: %s" % obj.id.name) - # Save this particle settings node, so we can check for it later - # when we process object changes - particle_settings_node = obj.id.original - - elif isinstance(obj.id, bpy.types.ShaderNodeTree): - if obj.id.name in bpy.data.node_groups: - # this is probably one of our fake node groups with ramps - # update all of the users of this node tree - rfb_log().debug("ShaderNodeTree updated: %s" % obj.id.name) - users = context.blend_data.user_map(subset={obj.id.original}) - for o in users[obj.id.original]: - if hasattr(o, 'rman_nodetree'): - o.rman_nodetree.update_tag() - elif hasattr(o, 'node_tree'): - o.node_tree.update_tag() + elif isinstance(dps_update.id, bpy.types.Material): + rfb_log().debug("Material updated: %s" % dps_update.id.name) + self.material_updated(dps_update) + + elif isinstance(dps_update.id, bpy.types.Mesh): + rfb_log().debug("Mesh updated: %s" % dps_update.id.name) + + elif isinstance(dps_update.id, bpy.types.Light): + self.check_light_datablock(dps_update) + + elif isinstance(dps_update.id, bpy.types.ParticleSettings): + self.check_particle_settings(dps_update) + + elif isinstance(dps_update.id, bpy.types.ShaderNodeTree): + self.check_shader_nodetree(dps_update) - elif isinstance(obj.id, bpy.types.Object): - particle_systems = getattr(obj.id, 'particle_systems', list()) - has_particle_systems = len(particle_systems) > 0 + elif isinstance(dps_update.id, bpy.types.Object): + self.check_object_datablock(dps_update) + + elif isinstance(dps_update.id, bpy.types.Collection): + rfb_log().debug("Collection updated: %s" % dps_update.id.name) + #self.update_collection(dps_update.id) + elif isinstance(dps_update.id, bpy.types.GeometryNodeTree): + # create an empty RmanUpdate + self.create_rman_update(dps_update.id.original, clear_instances=False) + else: + rfb_log().debug("Not handling %s update: %s" % (str(type(dps_update.id)), dps_update.id.name)) + + if not self.rman_updates and self.num_instances_changed: + # The number of instances changed, but we are not able + # to determine what changed. We are forced to check + # all instances. + rfb_log().debug("Set check_all_instances to True") + self.check_all_instances = True + + if self.check_all_instances or self.rman_updates: + self.check_instances() + + # call txmake all in case of new textures + texture_utils.get_txmanager().txmake_all(blocking=False) + self.rman_updates = dict() + rfb_log().debug("------End update scene----------") + + @time_this + def check_instances(self, batch_mode=False): + deleted_obj_keys = list(self.rman_scene.rman_prototypes) # list of potential objects to delete + already_udpated = list() # list of objects already updated during our loop + clear_instances = list() # list of objects who had their instances cleared + rfb_log().debug("Updating instances") + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + for instance in self.rman_scene.depsgraph.object_instances: + if instance.object.type in ('ARMATURE', 'CAMERA'): + continue - rman_type = object_utils._detect_primitive_(ob) - ob_data = obj.id.evaluated_get(self.rman_scene.depsgraph) - rman_sg_node = self.rman_scene.rman_objects.get(obj.id.original, None) + ob_key = instance.object.original + ob_eval = instance.object.evaluated_get(self.rman_scene.depsgraph) + instance_parent = None + psys = None + is_new_object = False + proto_key = object_utils.prototype_key(instance) + is_empty_instancer = False + if instance.is_instance: + ob_key = instance.instance_object.original + psys = instance.particle_system + instance_parent = instance.parent + is_empty_instancer = object_utils.is_empty_instancer(instance_parent) + + if proto_key in deleted_obj_keys: + deleted_obj_keys.remove(proto_key) + + rman_type = object_utils._detect_primitive_(ob_eval) - # NOTE: hide_get() and hide_viewport are two different things in Blender - # hide_get() hides the object from the viewport, but it does not actually remove the object - # as instances of the object can still be visible (ex: in particle systems) - # hide_viewport should be interpreted as an actual deleted object, including - # particle instances. - is_hidden = ob_data.hide_get() - + rman_sg_node = self.rman_scene.get_rman_prototype(proto_key) + if rman_sg_node and rman_type != rman_sg_node.rman_type: + # Types don't match + # + # This can happen because + # we have not been able to tag our types before Blender + # tells us an object has been added + # For now, just delete the existing sg_node + rfb_log().debug("\tTypes don't match. Removing: %s" % proto_key) + del self.rman_scene.rman_prototypes[proto_key] + rman_sg_node = None + + rman_update = self.rman_updates.get(ob_key, None) if not rman_sg_node: - rman_sg_node = self.update_objects_dict(obj.id, rman_type=rman_type) - - if self.do_add and not rman_sg_node: - rman_type = object_utils._detect_primitive_(ob_data) - - if ob_data.hide_get(): - # don't add if this hidden in the viewport - continue - if ob.type == 'CAMERA': - self.new_cameras.add(obj.id.original) - else: - if rman_type == 'EMPTY' and ob.is_instancer: - self.update_empty(ob) - else: - if rman_type == 'LIGHT': - # double check if this light is an rman light - # for now, we don't support adding Blender lights in IPR - # - # we can also get to this point when adding new rman lights because - # blender will tell us a new light has been added before we've had to chance - # to modify its properties to be an rman light, so we don't want to - # add this light just yet. - if not shadergraph_utils.is_rman_light(ob): - self.rman_scene.num_object_instances = prev_num_instances - rfb_log().debug("------End update scene----------") - return - elif rman_type == 'EMPTY': - # same issue can also happen with empty - # we have not been able to tag our types before Blender - # tells us an empty has been added - self.rman_scene.num_object_instances = prev_num_instances - rfb_log().debug("------End update scene----------") - return - rfb_log().debug("New object added: %s" % obj.id.name) - self.new_objects.add(obj.id.original) - self.update_instances.add(obj.id.original) - if rman_type == 'LIGHTFILTER': - # Add Light filters immediately, so that lights - # can reference them ASAP. - self.add_objects() - self.new_objects.remove(obj.id.original) - self.num_instances_changed = False - continue - - if rman_sg_node and rman_sg_node.sg_node: - # update db_name - db_name = object_utils.get_db_name(ob, rman_type=rman_type) - rman_sg_node.db_name = db_name - if self.update_object_visibility(rman_sg_node, ob_data): - continue - else: - continue - - if obj.is_updated_transform: - rfb_log().debug("Transform updated: %s" % obj.id.name) - if ob.type in ['CAMERA']: - # we deal with main camera transforms in view_draw - rman_sg_camera = self.rman_scene.rman_cameras[ob.original] - if rman_sg_camera == self.rman_scene.main_camera: - continue - translator = self.rman_scene.rman_translators['CAMERA'] - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - translator._update_render_cam_transform(ob, rman_sg_camera) + # this is a new object. + rman_sg_node = self.rman_scene.export_data_block(proto_key, ob_eval) + if not rman_sg_node: continue - + + rfb_log().debug("\tNew Object added: %s (%s)" % (proto_key, rman_type)) + if rman_type == 'LIGHTFILTER': - self._light_filter_transform_updated(obj) - elif rman_type == 'GPENCIL': - # FIXME: we shouldn't handle this specifically, but we seem to be - # hitting a prman crash when removing and adding instances of - # grease pencil curves - self._gpencil_transform_updated(obj) - elif rman_type == 'EMPTY': - self.update_empty(ob, rman_sg_node) - elif self.num_instances_changed: - rman_sg_node = self.rman_scene.rman_objects.get(obj.id.original, None) - for instance_obj in rman_sg_node.objects_instanced: - self.clear_instances(instance_obj) - self.update_instances.add(instance_obj) - rman_sg_node.objects_instanced.clear() - else: - # This is a simple transform. We don't clear the instances. - - # We always have to update particle systems when the object has transformed - # A transform changed can also be triggered when a particle system is removed. - self.update_particles.add(obj.id) - self.update_instances.add(obj.id.original) - self.update_geometry_node_instances(obj.id) - self.do_delete = False - if rman_type == 'LIGHT': - # check if portals are attached - self.update_portals(obj.id.original) - - # Check if this object is the focus object the camera. If it is - # we need to update the camera - rman_sg_camera = self.rman_scene.main_camera - if rman_sg_camera.rman_focus_object and rman_sg_camera.rman_focus_object == rman_sg_node: - translator = self.rman_scene.rman_translators['CAMERA'] - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - cam_object = translator.find_scene_camera() - translator.update(cam_object, rman_sg_camera) - - if obj.is_updated_geometry: - if is_hidden: - # don't update if this is hidden + # update all lights with this light filter + users = bpy.context.blend_data.user_map(subset={ob_eval.original}) + for o in users[ob_eval.original]: + if isinstance(o, bpy.types.Light): + o.node_tree.update_tag() + self.rman_scene.set_root_lightlinks() # update lightlinking on the root node continue - rfb_log().debug("Object updated: %s" % obj.id.name) - if has_particle_systems and particle_settings_node: - self.do_delete = False - self.update_particle_settings(obj, particle_settings_node) + is_new_object = True + if rman_update is None: + rman_update = self.create_rman_update(ob_key, update_geometry=False, update_shading=True, update_transform=True) + # set update_geometry to False + # since we've already exported the datablock + rman_update.is_updated_geometry = False + clear_instances.append(rman_sg_node) + + if self.check_all_instances: + # check all instances in the scene + # build an RmanUpdate instance if it doesn't exist + if rman_update is None: + rman_update = RmanUpdate() + rman_update.is_updated_shading = True + rman_update.is_updated_transform = True + if rman_sg_node.is_frame_sensitive and self.frame_number_changed: + rman_update.is_updated_geometry = True + self.rman_updates[ob_key] = rman_update + elif rman_update is None: + # no RmanUpdate exists for this object + # + # check if one of the users of this object updated + # ex: the object was instanced via a GeometryNodeTree, and the + # geometry node tree updated + users = self.rman_scene.context.blend_data.user_map(subset={ob_eval.original}) + user_exist = False + for o in users[ob_eval.original]: + if o.original in self.rman_updates: + rfb_log().debug("\t%s user updated (%s)" % (ob_eval.name, o.name)) + user_exist = True + break + if user_exist: + rman_update = self.create_rman_update(ob_key, update_transform=True) else: - # We always update particle systems in the non-num_instance_change case - # because the particle system can be pointing to a whole new particle settings - self.update_particles.add(obj.id) - - if not self.num_instances_changed: - self._obj_geometry_updated(obj) + # check if the instance_parent was the thing that + # changed + if not instance_parent: + continue + rman_update = self.rman_updates.get(instance_parent.original, None) + if rman_update is None: + continue + rfb_log().debug("\t%s parent updated (%s)" % (ob_eval.name, instance_parent.name)) + + if rman_sg_node and not is_new_object and not instance.is_instance: + if rman_update.is_updated_geometry and proto_key not in already_udpated: + translator = self.rman_scene.rman_translators.get(rman_type, None) + if rman_update.updated_prop_name: + rfb_log().debug("\tUpdating Single Primvar: %s" % proto_key) + translator.update_primvar(ob_eval, rman_sg_node, rman_update.updated_prop_name) + else: + rfb_log().debug("\tUpdating Object: %s" % proto_key) + translator.update(ob_eval, rman_sg_node) + rman_sg_node.shared_attrs.Clear() + self.update_particle_emitters(ob_eval) + already_udpated.append(proto_key) + + if rman_type in object_utils._RMAN_NO_INSTANCES_: + if rman_type == 'EMPTY': + self.rman_scene._export_hidden_instance(ob_eval, rman_sg_node) + continue - elif isinstance(obj.id, bpy.types.Collection): - # don't check the collection if we know objects - # were added or deleted in the scene. - if self.do_delete or self.do_add: - continue - - rfb_log().debug("Collection updated: %s" % obj.id.name) - self.update_collection(obj.id) + if rman_update.do_clear_instances: + # clear all instances for this prototype, if + # we have not already done so + if psys and instance_parent: + parent_proto_key = object_utils.prototype_key(instance_parent) + rman_parent_node = self.rman_scene.get_rman_prototype(parent_proto_key, ob=instance_parent) + if rman_parent_node and rman_parent_node not in clear_instances: + rfb_log().debug("\tClearing parent instances: %s" % parent_proto_key) + rman_parent_node.clear_instances() + clear_instances.append(rman_parent_node) + if rman_sg_node not in clear_instances: + rfb_log().debug("\tClearing instances: %s" % proto_key) + rman_sg_node.clear_instances() + clear_instances.append(rman_sg_node) + + if not self.rman_scene.check_visibility(instance): + # This instance is not visible in the viewport. Don't + # add an instance of it + continue - else: - self.update_geometry_node_instances(obj.id) + self.rman_scene.export_instance(ob_eval, instance, rman_sg_node, rman_type, instance_parent, psys) + else: + if rman_sg_node not in clear_instances: + # this might be a bit werid, but we don't want another RmanUpdate + # instance to clear the instances afterwards, so we add to the + # clear_instances list + clear_instances.append(rman_sg_node) + + # simply grab the existing instance and update the transform and/or material + rman_sg_group = self.rman_scene.get_rman_sg_instance(instance, rman_sg_node, instance_parent, psys, create=False) + if rman_sg_group: + if rman_update.is_updated_attributes: + from .rfb_utils import property_utils + attrs = rman_sg_group.sg_node.GetAttributes() + rm = ob_eval.renderman + if is_empty_instancer: + rm = instance_parent.renderman + meta = rm.prop_meta[rman_update.updated_prop_name] + rfb_log().debug("Setting RiAttribute: %s" % rman_update.updated_prop_name) + property_utils.set_riattr_bl_prop(attrs, rman_update.updated_prop_name, meta, rm, check_inherit=True) + rman_sg_group.sg_node.SetAttributes(attrs) + + if rman_update.is_updated_transform: + rman_group_translator = self.rman_scene.rman_translators['GROUP'] + rman_group_translator.update_transform(instance, rman_sg_group) + if is_empty_instancer and instance_parent.renderman.rman_material_override: + self.rman_scene.attach_material(instance_parent, rman_sg_group) + elif rman_update.is_updated_shading: + if psys: + self.rman_scene.attach_particle_material(psys.settings, instance_parent, ob_eval, rman_sg_group) + else: + self.rman_scene.attach_material(ob_eval, rman_sg_group) + else: + # Didn't get an rman_sg_group. Do a full instance export. + self.rman_scene.export_instance(ob_eval, instance, rman_sg_node, rman_type, instance_parent, psys) + + if not batch_mode: + if rman_type == 'LIGHT': + # We are dealing with a light. Check if it's a solo light, or muted + self.rman_scene.check_solo_light(rman_sg_node, ob_eval) - # call txmake all in case of new textures - texture_utils.get_txmanager().txmake_all(blocking=False) - # add new objs: - if self.new_objects: - self.add_objects() - elif self.do_add: - # if we didn't detect any new objects, but the number of - # instances changed, check our existing objects for object - # deletion and/or visibility - self.delete_objects() - - # delete any objects, if necessary - if self.do_delete: - self.delete_objects() - - # update any particle systems - self.update_particle_systems() - - # re-emit any instances needed - self.reemit_instances() + # check portal lights + self.update_portals(ob_eval) - rfb_log().debug("------End update scene----------") - - def add_objects(self): - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - rfb_log().debug("Adding new objects:") - self.rman_scene.export_data_blocks(self.new_objects) - - self.rman_scene.scene_any_lights = self.rman_scene._scene_has_lights() - if self.rman_scene.scene_any_lights: - self.rman_scene.default_light.SetHidden(1) - - def delete_objects(self): + # Hide the default light + if self.rman_scene.default_light.GetHidden() != 1: + self.rman_scene.default_light.SetHidden(1) + + # Delete any removed partcle systems + if proto_key in self.rman_scene.rman_particles: + ob_psys = self.rman_scene.rman_particles[proto_key] + rman_particle_nodes = list(ob_psys) + for psys in ob_eval.particle_systems: + try: + rman_particle_nodes.remove(psys.settings.original) + except: + continue + if rman_particle_nodes: + rfb_log().debug("\t\tRemoving particle nodes: %s" % proto_key) + for k in rman_particle_nodes: + del ob_psys[k] + + # delete objects + if deleted_obj_keys: + self.delete_objects(deleted_obj_keys) + + @time_this + def delete_objects(self, deleted_obj_keys=list()): rfb_log().debug("Deleting objects") - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - keys = [k for k in self.rman_scene.rman_objects.keys()] - for obj in keys: - try: - ob = self.rman_scene.bl_scene.objects.get(obj.name_full, None) - # NOTE: objects that are hidden from the viewport are considered deleted - # objects as well - if ob and not ob.hide_viewport: - rman_sg_node = self.rman_scene.rman_objects.get(obj, None) - if rman_sg_node: - # Double check object visibility - self.update_object_visibility(rman_sg_node, ob) - continue - except Exception as e: - pass - - rman_sg_node = self.rman_scene.rman_objects.get(obj, None) - if rman_sg_node: - for k,v in rman_sg_node.instances.items(): - if v.sg_node: - self.rman_scene.sg_scene.DeleteDagNode(v.sg_node) - rman_sg_node.instances.clear() - - # For now, don't delete the geometry itself - # there may be a collection instance still referencing the geo - - # self.rman_scene.sg_scene.DeleteDagNode(rman_sg_node.sg_node) - del self.rman_scene.rman_objects[obj] - - # We just deleted a light filter. We need to tell all lights - # associated with this light filter to update - if isinstance(rman_sg_node, RmanSgLightFilter): - for light_ob in rman_sg_node.lights_list: - light_key = object_utils.get_db_name(light_ob, rman_type='LIGHT') - rman_sg_light = self.rman_scene.rman_objects.get(light_ob.original, None) - if rman_sg_light: - self.rman_scene.rman_translators['LIGHT'].update_light_filters(light_ob, rman_sg_light) - try: - self.rman_scene.processed_obs.remove(obj) - except ValueError: - rfb_log().debug("Obj not in self.rman_scene.processed_obs: %s") - pass - - if self.rman_scene.render_default_light: - self.rman_scene.scene_any_lights = self.rman_scene._scene_has_lights() - if not self.rman_scene.scene_any_lights: - self.rman_scene.default_light.SetHidden(0) - + for key in deleted_obj_keys: + rman_sg_node = self.rman_scene.get_rman_prototype(key) + if not rman_sg_node: + continue + + rfb_log().debug("\tDeleting: %s" % rman_sg_node.db_name) + if key in self.rman_scene.rman_particles: + rfb_log().debug("\t\tRemoving particles...") + del self.rman_scene.rman_particles[key] + + del self.rman_scene.rman_prototypes[key] + + # We just deleted a light filter. We need to tell all lights + # associated with this light filter to update + if isinstance(rman_sg_node, RmanSgLightFilter): + self.rman_scene.get_root_sg_node().RemoveCoordinateSystem(rman_sg_node.sg_node) + for o in rman_sg_node.lights_list: + if o: + if hasattr(o, 'rman_nodetree'): + o.rman_nodetree.update_tag() + elif hasattr(o.data, 'node_tree'): + o.data.node_tree.update_tag() + + + if self.rman_scene.render_default_light: + self.rman_scene.scene_any_lights = self.rman_scene._scene_has_lights() + if not self.rman_scene.scene_any_lights: + self.rman_scene.default_light.SetHidden(0) + def update_cropwindow(self, cropwindow=None): if not self.rman_render.rman_interactive_running: return @@ -983,22 +1003,65 @@ def update_viewport_res_mult(self, context): translator.update_transform(None, rman_sg_camera) self.rman_scene.export_viewport_stats() - def update_global_options(self, context): + def update_global_options(self, prop_name, context): if not self.rman_render.rman_interactive_running: return + from .rfb_utils import property_utils self.rman_scene.bl_scene = context.scene + rm = self.rman_scene.bl_scene.renderman with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - self.rman_scene.export_global_options() - self.rman_scene.export_hider() - self.rman_scene.export_viewport_stats() + options = self.rman_scene.sg_scene.GetOptions() + meta = rm.prop_meta[prop_name] + rfb_log().debug("Update RiOption: %s" % prop_name) + property_utils.set_rioption_bl_prop(options, prop_name, meta, rm) + self.rman_scene.sg_scene.SetOptions(options) + self.rman_scene.export_viewport_stats() + - def update_root_node_func(self, context): + def update_root_node_func(self, prop_name, context): if not self.rman_render.rman_interactive_running: - return + return + from .rfb_utils import property_utils self.rman_scene.bl_scene = context.scene - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - self.rman_scene.export_root_sg_node() - + rm = self.rman_scene.bl_scene.renderman + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + root_sg = self.rman_scene.get_root_sg_node() + attrs = root_sg.GetAttributes() + meta = rm.prop_meta[prop_name] + rfb_log().debug("Update root node attribute: %s" % prop_name) + property_utils.set_riattr_bl_prop(attrs, prop_name, meta, rm, check_inherit=False) + root_sg.SetAttributes(attrs) + + def update_sg_node_riattr(self, prop_name, context, bl_object=None): + if not self.rman_render.rman_interactive_running: + return + self.rman_scene.bl_scene = context.scene + if bl_object: + ob = bl_object + else: + ob = context.object + rman_update = RmanUpdate() + rman_update.do_clear_instances = False + rman_update.is_updated_attributes = True + rman_update.updated_prop_name = prop_name + self.rman_updates[ob.original] = rman_update + rfb_log().debug("Updated RiAttribute: %s (%s)" % (rman_update.updated_prop_name, ob.name)) + + def update_sg_node_primvar(self, prop_name, context, bl_object=None): + if not self.rman_render.rman_interactive_running: + return + self.rman_scene.bl_scene = context.scene + if bl_object: + ob = bl_object + else: + ob = context.object + rman_update = RmanUpdate() + rman_update.do_clear_instances = False + rman_update.is_updated_geometry = True + rman_update.updated_prop_name = prop_name + self.rman_updates[ob.original] = rman_update + rfb_log().debug("Updated primvar: %s" % rman_update.updated_prop_name) + def update_material(self, mat): if not self.rman_render.rman_interactive_running: return @@ -1020,27 +1083,12 @@ def update_material(self, mat): def update_light(self, ob): if not self.rman_render.rman_interactive_running: return - rman_sg_light = self.rman_scene.rman_objects.get(ob.original, None) - if not rman_sg_light: - return - translator = self.rman_scene.rman_translators["LIGHT"] - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - translator.update(ob, rman_sg_light) - + ob.data.node_tree.update_tag() + def update_light_filter(self, ob): if not self.rman_render.rman_interactive_running: return - rman_sg_node = self.rman_scene.rman_objects.get(ob.original, None) - if not rman_sg_node: - return - - with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): - self.rman_scene.rman_translators['LIGHTFILTER'].update(ob, rman_sg_node) - for light_ob in rman_sg_node.lights_list: - light_key = object_utils.get_db_name(light_ob, rman_type='LIGHT') - rman_sg_light = self.rman_scene.rman_objects.get(light_ob.original, None) - if rman_sg_light: - self.rman_scene.rman_translators['LIGHT'].update_light_filters(light_ob, rman_sg_light) + ob.data.node_tree.update_tag() def update_solo_light(self, context): if not self.rman_render.rman_interactive_running: @@ -1051,17 +1099,7 @@ def update_solo_light(self, context): with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): for light_ob in scene_utils.get_all_lights(self.rman_scene.bl_scene, include_light_filters=False): - rman_sg_node = self.rman_scene.rman_objects.get(light_ob.original, None) - if not rman_sg_node: - continue - rm = light_ob.renderman - if not rm: - continue - - if rm.solo: - rman_sg_node.sg_node.SetHidden(0) - else: - rman_sg_node.sg_node.SetHidden(1) + light_ob.update_tag() def update_un_solo_light(self, context): if not self.rman_render.rman_interactive_running: @@ -1072,15 +1110,7 @@ def update_un_solo_light(self, context): with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): for light_ob in scene_utils.get_all_lights(self.rman_scene.bl_scene, include_light_filters=False): - rman_sg_node = self.rman_scene.rman_objects.get(light_ob.original, None) - if not rman_sg_node: - continue - rm = light_ob.renderman - if not rm: - continue - if self.rman_scene.check_light_local_view(light_ob, rman_sg_node): - continue - rman_sg_node.sg_node.SetHidden(light_ob.hide_get()) + light_ob.update_tag() def update_viewport_chan(self, context, chan_name): if not self.rman_render.rman_interactive_running: diff --git a/rman_sg_nodes/rman_sg_camera.py b/rman_sg_nodes/rman_sg_camera.py index 766e3fc6..847bbcd3 100644 --- a/rman_sg_nodes/rman_sg_camera.py +++ b/rman_sg_nodes/rman_sg_camera.py @@ -19,6 +19,7 @@ def __init__(self): self.screenwindow = None self.clip_start = -1 self.clip_end = -1 + self.dof_focal_length = -1 def __eq__(self, other): if self.res_width != other.res_width: @@ -55,6 +56,8 @@ def __eq__(self, other): return False if self.rman_fov != other.rman_fov: return False + if self.dof_focal_length != other.dof_focal_length: + return False return True class RmanSgCamera(RmanSgNode): diff --git a/rman_sg_nodes/rman_sg_fluid.py b/rman_sg_nodes/rman_sg_fluid.py index 07cbb561..b2339fb5 100644 --- a/rman_sg_nodes/rman_sg_fluid.py +++ b/rman_sg_nodes/rman_sg_fluid.py @@ -5,4 +5,14 @@ class RmanSgFluid(RmanSgNode): def __init__(self, rman_scene, sg_node, db_name): super().__init__(rman_scene, sg_node, db_name) self.rman_sg_volume_node = None - self.rman_sg_liquid_node = None \ No newline at end of file + self.rman_sg_liquid_node = None + + def __del__(self): + super().__del__() + pass + ''' + if self.rman_scene.rman_render.rman_running and self.rman_scene.sg_scene: + self.rman_scene.sg_scene.DeleteDagNode(self.rman_sg_volume_node) + self.rman_scene.sg_scene.DeleteDagNode(self.rman_sg_liquid_node) + super().__del__() + ''' \ No newline at end of file diff --git a/rman_sg_nodes/rman_sg_haircurves.py b/rman_sg_nodes/rman_sg_haircurves.py new file mode 100644 index 00000000..9b73554a --- /dev/null +++ b/rman_sg_nodes/rman_sg_haircurves.py @@ -0,0 +1,8 @@ +from .rman_sg_node import RmanSgNode + +class RmanSgHairCurves(RmanSgNode): + + def __init__(self, rman_scene, sg_node, db_name): + super().__init__(rman_scene, sg_node, db_name) + + self.sg_curves_list = list() diff --git a/rman_sg_nodes/rman_sg_mesh.py b/rman_sg_nodes/rman_sg_mesh.py index 294b538b..b14f7526 100644 --- a/rman_sg_nodes/rman_sg_mesh.py +++ b/rman_sg_nodes/rman_sg_mesh.py @@ -12,6 +12,13 @@ def __init__(self, rman_scene, sg_node, db_name): self.subdiv_scheme = 'none' self.is_multi_material = False self.multi_material_children = [] + self.sg_mesh = None + + def __del__(self): + if self.rman_scene.rman_render.rman_running and self.rman_scene.rman_render.sg_scene: + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + self.rman_scene.sg_scene.DeleteDagNode(self.sg_mesh) + super().__del__() @property def matrix_world(self): diff --git a/rman_sg_nodes/rman_sg_node.py b/rman_sg_nodes/rman_sg_node.py index f9c92ac3..6f293c24 100644 --- a/rman_sg_nodes/rman_sg_node.py +++ b/rman_sg_nodes/rman_sg_node.py @@ -1,3 +1,6 @@ +from ..rfb_logger import rfb_log + + class RmanSgNode(object): ''' RmanSgNode and subclasses are meant to be a thin layer class around a RixSceneGraph node. @@ -8,7 +11,7 @@ class RmanSgNode(object): db_name (str) - unique datablock name for this node instances (dict) - instances that uses this sg_node motion_steps (list) - the full list of motion time samples that are required for this Blender object - motion_steps (list) - the full list of deformation time samples that are required for this Blender object + deform_motion_steps (list) - the full list of deformation time samples that are required for this Blender object is_transforming (bool) - if this object is moving is_deforming (bool) - if this object is deforming rman_type (str) - the renderman type for this object @@ -16,6 +19,7 @@ class RmanSgNode(object): is_meshlight (bool) - if this object is a mesh light. is_hidden (bool) - whether this object is considered hidden is_frame_sensitive (bool) - indicates that the sg_node should be updated on frame changes + shared_attrs (RtParamList) - attributes that should be shared between all instances ''' def __init__(self, rman_scene, sg_node, db_name): @@ -47,12 +51,26 @@ def __init__(self, rman_scene, sg_node, db_name): # in texture paths self.is_frame_sensitive = False - # objects that this node creates as part of instancing - self.objects_instanced = set() - # psys self.bl_psys_settings = None + # attributes that should be shared with all instances + self.shared_attrs = rman_scene.rman.Types.ParamList() + + def __del__(self): + if self.rman_scene.rman_render.rman_running and self.rman_scene.rman_render.sg_scene: + with self.rman_scene.rman.SGManager.ScopedEdit(self.rman_scene.sg_scene): + #rfb_log().debug("Deleting dag node: %s" % self.db_name) + self.instances.clear() + if isinstance(self.sg_node, self.rman_scene.rman.scenegraph.Group): + self.rman_scene.sg_scene.DeleteDagNode(self.sg_node) + elif isinstance(self.sg_node, self.rman_scene.rman.scenegraph.Material): + self.rman_scene.sg_scene.DeleteMaterial(self.sg_node) + + def clear_instances(self): + if self.rman_scene.rman_render.rman_running: + self.instances.clear() + @property def rman_scene(self): return self.__rman_scene diff --git a/rman_spool.py b/rman_spool.py index d21c0607..c20391fd 100644 --- a/rman_spool.py +++ b/rman_spool.py @@ -2,8 +2,10 @@ import os import getpass import socket +import platform import datetime import bpy +from .rfb_utils import filepath_utils from .rfb_utils import string_utils from .rfb_utils.envconfig_utils import envconfig from .rfb_utils import display_utils @@ -21,6 +23,8 @@ def __init__(self, rman_render, rman_scene, depsgraph): self.rman_scene = rman_scene self.is_localqueue = True self.is_tractor = False + self.any_denoise = False + self.tractor_cfg = rfb_config['tractor_cfg'] if depsgraph: self.bl_scene = depsgraph.scene_eval self.depsgraph = depsgraph @@ -28,6 +32,12 @@ def __init__(self, rman_render, rman_scene, depsgraph): self.is_tractor = (self.bl_scene.renderman.queuing_system == 'tractor') def add_job_level_attrs(self, job): + dirmaps = get_pref('rman_tractor_dirmaps', []) + for dirmap in dirmaps: + job.newDirMap(src=dirmap.from_path, + dst=dirmap.to_path, + zone=dirmap.zone) + for k in rfb_config['dirmaps']: dirmap = rfb_config['dirmaps'][k] job.newDirMap(src=str(dirmap['from']), @@ -35,10 +45,57 @@ def add_job_level_attrs(self, job): zone=str(dirmap['zone'])) rman_vers = envconfig().build_info.version() + envkeys = [] if self.is_localqueue: - job.envkey = ['rmantree=%s' % envconfig().rmantree] + envkeys.append('rmantree=%s' % envconfig().rmantree) else: - job.envkey = ['prman-%s' % rman_vers] + envkeys.append('prman-%s' % rman_vers) + + user_envkeys = self.tractor_cfg.get('envkeys', get_pref('rman_tractor_envkeys')) + job.envkey = envkeys + user_envkeys.split() + + service = self.tractor_cfg.get('service', get_pref('rman_tractor_service')) + job.service = service + job.priority = float(self.tractor_cfg.get('priority', get_pref('rman_tractor_priority'))) + job.metadata = self.tractor_cfg.get('metadata', get_pref('rman_tractor_metadata')) + job.comment = self.tractor_cfg.get('comment', get_pref('rman_tractor_comment')) + + crews = self.tractor_cfg.get('crews', get_pref('rman_tractor_crews')) + if crews != '': + job.crews = crews.split() + projects = self.tractor_cfg.get('projects', get_pref('rman_tractor_projects')) + if projects != '': + job.projects = projects.split() + + whendone = self.tractor_cfg.get('whendone', get_pref('rman_tractor_whendone')) + whenerror = self.tractor_cfg.get('whenerror', get_pref('rman_tractor_whenerror')) + whenalways = self.tractor_cfg.get('whenalways', get_pref('rman_tractor_whenalways')) + if whendone != '': + job.newPostscript(argv=whendone, when="done", service=service) + if whenerror != '': + job.newPostscript(argv=whenerror, when="error", service=service) + if whenalways != '': + job.newPostscript(argv=whenalways, when="always", service=service) + + after = self.tractor_cfg.get('after', get_pref('rman_tractor_after')) + if after != '': + try: + aftersplit = after.split(' ') + if len(aftersplit) == 2: + t_date = aftersplit[0].split('/') + t_time = aftersplit[1].split(':') + if len(t_date) == 2 and len(t_time) == 2: + today = datetime.datetime.today() + job.after = datetime.datetime(today.year, int(t_date[0]), + int(t_date[1]), + int(t_time[0]), + int(t_time[1]), 0) + else: + print('Could not parse after date: %s. Ignoring.' % after) + else: + print('Could not parse after date: %s. Ignoring.' % after) + except: + print('Could not parse after date: %s. Ignoring.' % after) def _add_additional_prman_args(self, args): rm = self.bl_scene.renderman @@ -55,6 +112,17 @@ def _add_additional_prman_args(self, args): args.append('%r') scene_utils.set_render_variant_spool(self.bl_scene, args, self.is_tractor) + + def add_preview_task(self, task, imgs): + if imgs is None: + return + it_path = envconfig().rman_it_path + if platform.system() == 'Windows': + it_path = '"%s"' % it_path + img = imgs + if isinstance(imgs, list): + img = " ".join(imgs) + task.preview = "%s %s" % (it_path, img) def add_prman_render_task(self, parentTask, title, threads, rib, img): rm = self.bl_scene.renderman @@ -62,8 +130,7 @@ def add_prman_render_task(self, parentTask, title, threads, rib, img): task = author.Task() task.title = title - if img: - task.preview = 'sho %s' % str(img) + self.add_preview_task(task, img) command = author.Command(local=False, service="PixarRender") command.argv = ["prman"] @@ -82,36 +149,49 @@ def add_prman_render_task(self, parentTask, title, threads, rib, img): task.addCommand(command) parentTask.addChild(task) - def add_blender_render_task(self, frame, parentTask, title, bl_filename, img): + def add_blender_render_task(self, frame, parentTask, title, bl_filename, chunk=None): rm = self.bl_scene.renderman - out_dir = '' task = author.Task() task.title = title - if img: - task.preview = 'sho %s' % str(img) command = author.Command(local=False, service="PixarRender") - bl_blender_path = bpy.app.binary_path + bl_blender_path = 'blender' + if self.is_localqueue: + bl_blender_path = bpy.app.binary_path command.argv = [bl_blender_path] command.argv.append('-b') command.argv.append('%%D(%s)' % bl_filename) - command.argv.append('-f') - command.argv.append(str(frame)) + begin = frame + end = frame + if chunk: + command.argv.append('-f') + command.argv.append('%s..%s' % (str(frame), str(frame+(chunk)))) + end = frame+chunk + else: + command.argv.append('-f') + command.argv.append(str(frame)) task.addCommand(command) - parentTask.addChild(task) - - def generate_blender_batch_tasks(self, anim, parent_task, tasktitle, - start, last, by, bl_filename): - rm = self.bl_scene.renderman + imgs = list() + dspys_dict = display_utils.get_dspy_dict(self.rman_scene, expandTokens=False) + for i in range(begin, end+1): + img = string_utils.expand_string(dspys_dict['displays']['beauty']['filePath'], + frame=i, + asFilePath=True) + imgs.append(img) - if anim is False: - img_expanded = '' + self.add_preview_task(task, imgs) + parentTask.addChild(task) + def generate_blender_batch_tasks(self, anim, parent_task, tasktitle, + start, last, by, chunk, bl_filename): + + if anim is False: + frametasktitle = ("%s Frame: %d " % (tasktitle, int(start))) frametask = author.Task() @@ -121,7 +201,7 @@ def generate_blender_batch_tasks(self, anim, parent_task, tasktitle, prmantasktitle = "%s (render)" % frametasktitle self.add_blender_render_task(start, frametask, prmantasktitle, - bl_filename, img_expanded) + bl_filename) parent_task.addChild(frametask) @@ -134,15 +214,25 @@ def generate_blender_batch_tasks(self, anim, parent_task, tasktitle, (str(self.depsgraph.view_layer.name))) renderframestask.title = renderframestasktitle - for iframe in range(int(start), int(last + 1), int(by)): - - img_expanded = '' - - prmantasktitle = ("%s Frame: %d (blender)" % - (tasktitle, int(iframe))) - - self.add_blender_render_task(iframe, renderframestask, prmantasktitle, - bl_filename, img_expanded) + if (chunk+1) > 1 and by < 2 and (start+chunk < last+1): + iframe = start + while (iframe < last+1): + diff = chunk + if ((iframe + chunk) > last+1): + diff = (last) - iframe + prmantasktitle = ("%s Frames: (%d-%d) (blender)" % + (tasktitle, iframe, iframe+diff)) + + self.add_blender_render_task(iframe, renderframestask, prmantasktitle, + bl_filename, chunk=diff) + iframe += diff+1 + else: + for iframe in range(start, last + 1, by): + prmantasktitle = ("%s Frame: %d (blender)" % + (tasktitle, int(iframe))) + + self.add_blender_render_task(iframe, renderframestask, prmantasktitle, + bl_filename) parent_task.addChild(renderframestask) @@ -204,9 +294,117 @@ def generate_rib_render_tasks(self, anim, parent_task, tasktitle, parent_task.addChild(renderframestask) + def generate_aidenoise_tasks(self, start, last, by): + tasktitle = "Denoiser Renders" + parent_task = author.Task() + parent_task.title = tasktitle + rm = self.bl_scene.renderman + dspys_dict = display_utils.get_dspy_dict(self.rman_scene, expandTokens=False) + + img_files = list() + preview_img_files = list() + have_variance = False + beauty_path = dspys_dict['displays']['beauty']['filePath'] + variance_file = string_utils.expand_string(beauty_path, + frame=1, + asFilePath=True) + path = filepath_utils.get_real_path(rm.ai_denoiser_output_dir) + if not os.path.exists(path): + path = os.path.join(os.path.dirname(variance_file), 'denoised') + do_cross_frame = (rm.ai_denoiser_mode == 'crossframe') + if by > 1: + do_cross_frame = False # can't do crossframe if by > 1 + for frame_num in range(start, last + 1, by): + self.rman_render.bl_frame_current = frame_num + + variance_file = string_utils.expand_string(beauty_path, + frame=frame_num, + asFilePath=True) + + for dspy,params in dspys_dict['displays'].items(): + if not params['denoise']: + continue + if dspy == 'beauty': + if not have_variance: + have_variance = True + img_files.append(variance_file) + preview_img_files.append(os.path.join(path, os.path.basename(variance_file))) + else: + token_dict = {'aov': dspy} + aov_file = string_utils.expand_string(params['filePath'], + frame=frame_num, + token_dict=token_dict, + asFilePath=True) + img_files.append(aov_file) + else: + # use frame range format + # ex: foo.####.exr start-last + for frame_num in range(start, last + 1): + variance_file = string_utils.expand_string(beauty_path, + frame=frame_num, + asFilePath=True) + preview_img_files.append(os.path.join(path, os.path.basename(variance_file))) + + for dspy,params in dspys_dict['displays'].items(): + if not params['denoise']: + continue + if dspy == 'beauty': + if not have_variance: + have_variance = True + variance_file = string_utils.expand_string(beauty_path, + frame='#', + asFilePath=True) + img_files.append(variance_file) + else: + token_dict = {'aov': dspy} + aov_file = string_utils.expand_string(params['filePath'], + token_dict=token_dict, + frame="#", + asFilePath=True) + img_files.append(aov_file) + + img_files.append('%d-%d' % (start, last)) + + if not have_variance or len(img_files) < 1: + return None + + if start == last: + do_cross_frame = False + + cur_frame = self.rman_scene.bl_frame_current + task = author.Task() + if do_cross_frame: + task.title = 'Denoise Frames %d - %d (Cross Frame)' % (start, last) + else: + task.title = 'Denoise Frames (%d - %d)' % (start, last) + + command = author.Command(local=False, service="PixarRender") + + command.argv = ["denoise_batch"] + + if do_cross_frame: + command.argv.append('--crossframe') + + if rm.ai_denoiser_verbose: + command.argv.append('-v') + command.argv.append('-a') + command.argv.append('%.3f' % rm.ai_denoiser_asymmetry) + command.argv.append('-o') + command.argv.append(path) + if rm.ai_denoiser_flow: + command.argv.append('-f') + command.argv.extend(img_files) + + task.addCommand(command) + self.add_preview_task(task, preview_img_files) + parent_task.addChild(task) + + self.rman_render.bl_frame_current = cur_frame + return parent_task + def generate_denoise_tasks(self, start, last, by): - tasktitle = "Denoiser Renders" + tasktitle = "Denoiser (Legacy) Renders" parent_task = author.Task() parent_task.title = tasktitle rm = self.bl_scene.renderman @@ -236,10 +434,10 @@ def generate_denoise_tasks(self, start, last, by): # for crossframe, do it all in one task cur_frame = self.rman_scene.bl_frame_current task = author.Task() - task.title = 'Denoise Cross Frame' + task.title = 'Denoise (Legacy) Cross Frame' command = author.Command(local=False, service="PixarRender") - command.argv = ["denoise"] + command.argv = ["denoise_legacy"] for opt in denoise_options: command.argv.append(opt) command.argv.append('--crossframe') @@ -266,8 +464,10 @@ def generate_denoise_tasks(self, start, last, by): command.argv.append(variance_file) else: command.argv.append(variance_file) + token_dict = {'aov': dspy} aov_file = string_utils.expand_string(params['filePath'], frame=frame_num, + token_dict=token_dict, asFilePath=True) command.argv.append(aov_file) @@ -297,18 +497,20 @@ def generate_denoise_tasks(self, start, last, by): continue task = author.Task() - task.title = 'Denoise Frame %d' % frame_num + task.title = 'Denoise (Legacy) Frame %d' % frame_num command = author.Command(local=False, service="PixarRender") - command.argv = ["denoise"] + command.argv = ["denoise_legacy"] for opt in denoise_options: command.argv.append(opt) if dspy == 'beauty': command.argv.append(variance_file) else: command.argv.append(variance_file) + token_dict = {'aov': dspy} aov_file = string_utils.expand_string(params['filePath'], frame=frame_num, + token_dict=token_dict, asFilePath=True) command.argv.append(aov_file) @@ -324,7 +526,9 @@ def blender_batch_render(self, bl_filename): rm = scene.renderman frame_begin = self.bl_scene.frame_start frame_end = self.bl_scene.frame_end + frame_current = self.bl_scene.frame_current by = self.bl_scene.frame_step + chunk = min(rm.bl_batch_frame_chunk, frame_end)-1 # update variables string_utils.set_var('scene', self.bl_scene.name.replace(' ', '_')) @@ -362,14 +566,20 @@ def blender_batch_render(self, bl_filename): anim = (frame_begin != frame_end) self.generate_blender_batch_tasks(anim, parent_task, tasktitle, - frame_begin, frame_end, by, bl_filename) + frame_begin, frame_end, by, chunk, bl_filename) job.addChild(parent_task) - # Don't denoise if we're baking - if rm.hider_type == 'RAYTRACE': - parent_task = self.generate_denoise_tasks(frame_begin, frame_end, by) - job.addChild(parent_task) + # Don't generate denoise tasks if we're baking + # or using the Blender compositor + if rm.hider_type == 'RAYTRACE' and (not rm.use_bl_compositor or not scene.use_nodes): + if rm.use_legacy_denoiser: + parent_task = self.generate_denoise_tasks(frame_begin, frame_end, by) + else: + parent_task = self.generate_aidenoise_tasks(frame_begin, frame_end, by) + + if parent_task: + job.addChild(parent_task) scene_filename = bpy.data.filepath if scene_filename == '': @@ -402,6 +612,7 @@ def blender_batch_render(self, bl_filename): return self.spool(job, jobfile) + string_utils.update_frame_token(frame_current) def batch_render(self): @@ -410,6 +621,7 @@ def batch_render(self): rm = scene.renderman frame_begin = self.bl_scene.frame_start frame_end = self.bl_scene.frame_end + frame_current = self.bl_scene.frame_current by = self.bl_scene.frame_step if not rm.external_animation: @@ -446,8 +658,13 @@ def batch_render(self): # Don't denoise if we're baking if rm.hider_type == 'RAYTRACE': - parent_task = self.generate_denoise_tasks(frame_begin, frame_end, by) - job.addChild(parent_task) + if rm.use_legacy_denoiser: + parent_task = self.generate_denoise_tasks(frame_begin, frame_end, by) + else: + parent_task = self.generate_aidenoise_tasks(frame_begin, frame_end, by) + + if parent_task: + job.addChild(parent_task) bl_filename = bpy.data.filepath if bl_filename == '': @@ -474,6 +691,7 @@ def batch_render(self): return self.spool(job, jobfile) + string_utils.update_frame_token(frame_current) def spool(self, job, jobfile): @@ -496,14 +714,15 @@ def spool(self, job, jobfile): subprocess.Popen(args, env=env) else: # spool to tractor - tractor_engine ='tractor-engine' - tractor_port = '80' + tractor_engine = get_pref("rman_tractor_hostname") + tractor_port = str(get_pref("rman_tractor_port")) owner = getpass.getuser() + if not get_pref("rman_tractor_local_user"): + owner = get_pref("rman_tractor_user") - tractor_cfg = rfb_config['tractor_cfg'] - tractor_engine = tractor_cfg.get('engine', tractor_engine) - tractor_port = str(tractor_cfg.get('port', tractor_port)) - owner = tractor_cfg.get('user', owner) + tractor_engine = self.tractor_cfg.get('engine', tractor_engine) + tractor_port = str(self.tractor_cfg.get('port', tractor_port)) + owner = self.tractor_cfg.get('user', owner) # env var trumps rfb.json tractor_env = envconfig().getenv('TRACTOR_ENGINE') diff --git a/rman_stats/__init__.py b/rman_stats/__init__.py index bba7724e..91c1994a 100644 --- a/rman_stats/__init__.py +++ b/rman_stats/__init__.py @@ -1,13 +1,12 @@ from ..rfb_logger import rfb_log -import re import os -import sys import json import bpy import rman import threading import time +import getpass from collections import OrderedDict import rman_utils.stats_config.core as stcore @@ -19,6 +18,7 @@ __LIVE_METRICS__ = [ ["/system.processMemory", "Memory"], + ["/rman/renderer@isRendering", None], ["/rman/renderer@progress", None], ['/rman@iterationComplete', None], ["/rman.timeToFirstRaytrace", "First Ray"], @@ -110,6 +110,7 @@ def __init__(self, rman_render): self._prevTotalRays = 0 self._progress = 0 self._prevTotalRaysValid = True + self._isRendering = False for name,label in __LIVE_METRICS__: if name: @@ -157,7 +158,8 @@ def reset(self): self._progress = 0 self._prevTotalRaysValid = True self.export_stat_label = '' - self.export_stat_progress = 0.0 + self.export_stat_progress = 0.0 + self._isRendering = True def create_stats_manager(self): if self.mgr: @@ -181,18 +183,21 @@ def init_stats_session(self): self.rman_stats_session_config.LoadConfigFile(rman_stats_config_path, 'stats.ini') # do this once at startup - self.web_socket_server_id = 'rfb_statsserver_'+str(os.getpid()) + self.web_socket_server_id = 'rfb_statsserver_' + getpass.getuser() + '_' + str(os.getpid()) self.rman_stats_session_config.SetServerId(self.web_socket_server_id) # initialize session config with prefs, then add session self.update_session_config() self.rman_stats_session = rman.Stats.AddSession(self.rman_stats_session_config) - def update_session_config(self): + def update_session_config(self, force_enabled=False): self.web_socket_enabled = prefs_utils.get_pref('rman_roz_liveStatsEnabled', default=False) self.web_socket_port = prefs_utils.get_pref('rman_roz_webSocketServer_Port', default=0) + if force_enabled: + self.web_socket_enabled = True + config_dict = dict() config_dict["logLevel"] = int(prefs_utils.get_pref('rman_roz_logLevel', default='3')) config_dict["webSocketPort"] = self.web_socket_port @@ -242,10 +247,15 @@ def boot_strap(self): self.mgr.enableMetric(name) return - def attach(self): + def attach(self, force=False): if not self.mgr: return + + if force: + # force the live stats to be enabled + self.update_session_config(force_enabled=True) + if (self.mgr.clientConnected()): return @@ -347,6 +357,9 @@ def update_payloads(self): self.render_live_stats["Total Rays"] = currentTotalRays self._prevTotalRaysValid = True self._prevTotalRays = currentTotalRays + elif name == "/rman/renderer@isRendering": + is_rendering = dat['payload'] + self._isRendering = is_rendering elif name == "/rman@iterationComplete": itr = dat['payload'][0] self._iterations = itr @@ -437,17 +450,9 @@ def draw_render_stats(self): return def register(): - if prefs_utils.get_pref('rman_ui_framework') == 'QT': - try: - from . import operators - operators.register() - except: - pass + from . import operators + operators.register() def unregister(): - if prefs_utils.get_pref('rman_ui_framework') == 'QT': - try: - from . import operators - operators.unregister() - except: - pass \ No newline at end of file + from . import operators + operators.unregister() \ No newline at end of file diff --git a/rman_stats/operators.py b/rman_stats/operators.py index 78065af3..c443bc0a 100644 --- a/rman_stats/operators.py +++ b/rman_stats/operators.py @@ -1,8 +1,3 @@ -try: - from ..rman_ui import rfb_qt -except: - raise - import bpy import sys from ..rfb_logger import rfb_log @@ -10,56 +5,78 @@ __STATS_WINDOW__ = None -class LiveStatsQtAppTimed(rfb_qt.RfbBaseQtAppTimed): - bl_idname = "wm.live_stats_qt_app_timed" - bl_label = "Live Stats" - - def __init__(self): - super(LiveStatsQtAppTimed, self).__init__() - - def execute(self, context): - self._window = RmanStatsWrapper() - return super(LiveStatsQtAppTimed, self).execute(context) - -class RmanStatsWrapper(rfb_qt.RmanQtWrapper): +if not bpy.app.background: + from ..rman_ui import rfb_qt - def __init__(self): - super(RmanStatsWrapper, self).__init__() + class LiveStatsQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.live_stats_qt_app_timed" + bl_label = "Live Stats" - # import here because we will crash Blender - # when we try to import it globally - import rman_utils.stats_config.ui as rui + def __init__(self): + super(LiveStatsQtAppTimed, self).__init__() - self.resize(512, 512) - self.setWindowTitle('RenderMan Live Stats') + def execute(self, context): + global __STATS_WINDOW__ + __STATS_WINDOW__ = RmanStatsWrapper() + self._window = __STATS_WINDOW__ + return super(LiveStatsQtAppTimed, self).execute(context) - rr = rman_render.RmanRender.get_rman_render() - mgr = rr.stats_mgr.mgr - self.ui = rui.StatsManagerUI(self, manager=mgr, show_connect=True, show_config=False) - self.setLayout(self.ui.topLayout) - self.show() # Show window - - def closeEvent(self, event): - event.accept() - -class PRMAN_OT_Open_Stats(bpy.types.Operator): - bl_idname = "renderman.rman_open_stats" - bl_label = "Live Stats" - - def execute(self, context): - - global __STATS_WINDOW__ - if sys.platform == "darwin": - rfb_qt.run_with_timer(__STATS_WINDOW__, RmanStatsWrapper) - else: - bpy.ops.wm.live_stats_qt_app_timed() - - return {'RUNNING_MODAL'} - -classes = [ - PRMAN_OT_Open_Stats, - LiveStatsQtAppTimed -] + class RmanStatsWrapper(rfb_qt.RmanQtWrapper): + + def __init__(self): + super(RmanStatsWrapper, self).__init__() + + # import here because we will crash Blender + # when we try to import it globally + import rman_utils.stats_config.ui as rui + + self.resize(512, 512) + self.setWindowTitle('RenderMan Live Stats') + + rr = rman_render.RmanRender.get_rman_render() + mgr = rr.stats_mgr.mgr + self.ui = rui.StatsManagerUI(self, manager=mgr, show_connect=True, show_config=False) + self.setLayout(self.ui.topLayout) + self.show() # Show window + + def show(self): + if not self.ui.manager.clientConnected(): + self.ui.attachCB() + else: + # This is a bit weird. If the stats manager is already + # connected, the UI doesn't seem to update the connection status when + # first showing the window. + # For now, just kick the UI's connectedTimer + self.ui.connectedTimer.start(1000) + self.ui.attachBtn.setText("Connecting...") + + super(RmanStatsWrapper, self).show() + + def closeEvent(self, event): + event.accept() + + class PRMAN_OT_Open_Stats(bpy.types.Operator): + bl_idname = "renderman.rman_open_stats" + bl_label = "Live Stats" + + def execute(self, context): + + global __STATS_WINDOW__ + if __STATS_WINDOW__ and __STATS_WINDOW__.isVisible(): + return {'FINISHED'} + + if sys.platform == "darwin": + __STATS_WINDOW__ = rfb_qt.run_with_timer(__STATS_WINDOW__, RmanStatsWrapper) + else: + bpy.ops.wm.live_stats_qt_app_timed() + + return {'FINISHED'} + +classes = [] + +if not bpy.app.background: + classes.append(PRMAN_OT_Open_Stats) + classes.append(LiveStatsQtAppTimed) def register(): from ..rfb_utils import register_utils diff --git a/rman_translators/rman_alembic_translator.py b/rman_translators/rman_alembic_translator.py index 0c6f9e5f..b3295e33 100644 --- a/rman_translators/rman_alembic_translator.py +++ b/rman_translators/rman_alembic_translator.py @@ -35,7 +35,9 @@ def update(self, ob, rman_sg_alembic): abc_frame = rm.abc_frame if rm.abc_use_scene_frame: rman_sg_alembic.is_frame_sensitive = True - abc_frame = float(self.rman_scene.bl_frame_current) + abc_frame = float(self.rman_scene.bl_frame_current) + elif string_utils.check_frame_sensitive(abc_filepath): + rman_sg_alembic.is_frame_sensitive = True else: rman_sg_alembic.is_frame_sensitive = False @@ -48,6 +50,6 @@ def update(self, ob, rman_sg_alembic): abc_args += " -ccw" primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_data, abc_args) - + super().export_object_primvars(ob, primvar) rman_sg_alembic.sg_node.SetPrimVars(primvar) diff --git a/rman_translators/rman_blobby_translator.py b/rman_translators/rman_blobby_translator.py index 5769d331..f01201e2 100644 --- a/rman_translators/rman_blobby_translator.py +++ b/rman_translators/rman_blobby_translator.py @@ -93,5 +93,5 @@ def update(self, ob, rman_sg_blobby): rman_sg_blobby.sg_node.Define(count) primvar.SetIntegerArray(self.rman_scene.rman.Tokens.Rix.k_Ri_code, op, len(op)) primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_floats, tform, len(tform)) - #primvar.SetFloat(self.rman_scene.rman.Tokens.Rix.k_displacementbound_sphere, rm.displacementbound) + super().export_object_primvars(ob, primvar) rman_sg_blobby.sg_node.SetPrimVars(primvar) diff --git a/rman_translators/rman_brickmap_translator.py b/rman_translators/rman_brickmap_translator.py index 964959ca..7be5cb04 100644 --- a/rman_translators/rman_brickmap_translator.py +++ b/rman_translators/rman_brickmap_translator.py @@ -24,6 +24,11 @@ def export_deform_sample(self, rman_sg_brickmap, ob, time_sample): def update(self, ob, rman_sg_brickmap): primvar = rman_sg_brickmap.sg_node.GetPrimVars() rm = ob.renderman + if string_utils.check_frame_sensitive(rm.bkm_filepath): + rman_sg_brickmap.is_frame_sensitive = True + else: + rman_sg_brickmap.is_frame_sensitive = False bkm_filepath = string_utils.expand_string(rm.bkm_filepath) primvar.SetString("filename", bkm_filepath) + super().export_object_primvars(ob, primvar) rman_sg_brickmap.sg_node.SetPrimVars(primvar) \ No newline at end of file diff --git a/rman_translators/rman_camera_translator.py b/rman_translators/rman_camera_translator.py index 33795b23..45f4bf3c 100644 --- a/rman_translators/rman_camera_translator.py +++ b/rman_translators/rman_camera_translator.py @@ -50,7 +50,6 @@ def _update_viewport_transform(self, rman_sg_camera): def _update_render_cam_transform(self, ob, rman_sg_camera, index=0, seg=0.0): - cam = ob.data mtx = ob.matrix_world # normalize the matrix @@ -61,7 +60,7 @@ def _update_render_cam_transform(self, ob, rman_sg_camera, index=0, seg=0.0): v = transform_utils.convert_matrix(mtx) if rman_sg_camera.cam_matrix == v: - return + return False rman_sg_camera.cam_matrix = v if rman_sg_camera.is_transforming: @@ -69,6 +68,7 @@ def _update_render_cam_transform(self, ob, rman_sg_camera, index=0, seg=0.0): else: rman_sg_camera.sg_node.SetTransform( v ) + return True def update_transform(self, ob, rman_sg_camera, index=0, seg=0): if self.rman_scene.is_viewport_render: @@ -456,17 +456,22 @@ def update_viewport_cam(self, ob, rman_sg_camera, force_update=False): rman_sg_camera.use_focus_object = cam_rm.rman_focus_object if cam_rm.rman_focus_object: dof_focal_distance = (ob.location - cam_rm.rman_focus_object.location).length - rman_sg_node = self.rman_scene.rman_objects.get(cam_rm.rman_focus_object.original, None) - rman_sg_camera.rman_focus_object = rman_sg_node + rman_sg_camera.bl_cam_props.dof_focal_length = dof_focal_distance + rman_sg_node = self.rman_scene.get_rman_prototype(object_utils.prototype_key(cam_rm.rman_focus_object)) + if rman_sg_node: + rman_sg_camera.rman_focus_object = rman_sg_node else: dof_focal_distance = cam_rm.rman_focus_distance + rman_sg_camera.bl_cam_props.dof_focal_length = dof_focal_distance rman_sg_camera.rman_focus_object = None if dof_focal_distance > 0.0: dof_focal_length = (cam.lens * 0.001) + rman_sg_camera.bl_cam_props.dof_focal_length = dof_focal_distance projparams.SetFloat(self.rman_scene.rman.Tokens.Rix.k_fStop, cam_rm.rman_aperture_fstop) projparams.SetFloat(self.rman_scene.rman.Tokens.Rix.k_focalLength, dof_focal_length) projparams.SetFloat(self.rman_scene.rman.Tokens.Rix.k_focalDistance, dof_focal_distance) else: + rman_sg_camera.bl_cam_props.dof_focal_length = -1 rman_sg_camera.use_focus_object = False rman_sg_camera.rman_focus_object = None @@ -527,8 +532,9 @@ def _set_fov(self, ob, rman_sg_camera, cam, aspectratio, projparams): rman_sg_camera.use_focus_object = cam_rm.rman_focus_object if cam_rm.rman_focus_object: dof_focal_distance = (ob.location - cam_rm.rman_focus_object.location).length - rman_sg_node = self.rman_scene.rman_objects.get(cam_rm.rman_focus_object.original, None) - rman_sg_camera.rman_focus_object = rman_sg_node + rman_sg_node = self.rman_scene.get_rman_prototype(object_utils.prototype_key(cam_rm.rman_focus_object)) + if rman_sg_node: + rman_sg_camera.rman_focus_object = rman_sg_node else: dof_focal_distance = cam_rm.rman_focus_distance rman_sg_camera.rman_focus_object = None diff --git a/rman_translators/rman_curve_translator.py b/rman_translators/rman_curve_translator.py index cb570d7e..f0e257e7 100644 --- a/rman_translators/rman_curve_translator.py +++ b/rman_translators/rman_curve_translator.py @@ -6,6 +6,7 @@ import bpy import math +import numpy as np def get_bspline_curve(curve): P = [] @@ -13,22 +14,24 @@ def get_bspline_curve(curve): nvertices = [] name = '' num_curves = len(curve.splines) - index = [] - for i, spline in enumerate(curve.splines): + for spline in curve.splines: - width = [] - for bp in spline.points: - P.append([bp.co[0], bp.co[1], bp.co[2]]) - w = bp.radius * 0.01 - if w < 0.01: - w = 0.01 - width.extend( 3 * [w]) + npoints = len(spline.points) + pts = np.zeros(npoints*4, dtype=np.float32) + width = np.zeros(npoints, dtype=np.float32) - widths.append(width) - index.append(i) - nvertices.append(len(spline.points)) + spline.points.foreach_get('co', pts) + spline.points.foreach_get('radius', width) + pts = np.reshape(pts, (npoints, 4)) + width = np.where(width >= 1.0, width*0.01, 0.01) + + P.extend(pts[0:, 0:3].tolist()) + widths.append(width.tolist()) + nvertices.append(npoints) name = spline.id_data.name + + index = np.arange(num_curves).tolist() return (P, num_curves, nvertices, widths, index, name) @@ -38,22 +41,24 @@ def get_curve(curve): nvertices = [] name = '' num_curves = len(curve.splines) - index = [] - for i, spline in enumerate(curve.splines): + for spline in curve.splines: - width = [] - for bp in spline.points: - P.append([bp.co[0], bp.co[1], bp.co[2]]) - w = bp.radius * 0.01 - if w < 0.01: - w = 0.01 - width.extend( 3 * [w]) + npoints = len(spline.points) + pts = np.zeros(npoints*4, dtype=np.float32) + width = np.zeros(npoints, dtype=np.float32) + + spline.points.foreach_get('co', pts) + spline.points.foreach_get('radius', width) + pts = np.reshape(pts, (npoints, 4)) + width = np.where(width >= 1.0, width*0.01, 0.01) + P.extend(pts[0:, 0:3].tolist()) widths.append(width) - index.append(i) - nvertices.append(len(spline.points)) + nvertices.append(npoints) name = spline.id_data.name + + index = np.arange(num_curves).tolist() return (P, num_curves, nvertices, widths, index, name) @@ -68,7 +73,10 @@ def get_bezier_curve(curve): P.append(bp.handle_left) P.append(bp.co) P.append(bp.handle_right) - width.extend( 3 * [bp.radius * 0.01]) + w = bp.radius * 0.01 + if w < 0.01: + w = 0.01 + width.extend( 3 * [w]) if spline.use_cyclic_u: period = 'periodic' @@ -108,7 +116,7 @@ def __init__(self, rman_scene): def export(self, ob, db_name): sg_node = self.rman_scene.sg_scene.CreateGroup(db_name) - is_mesh = self._is_mesh(ob) + is_mesh = object_utils.curve_is_mesh(ob) rman_sg_curve = RmanSgCurve(self.rman_scene, sg_node, db_name) rman_sg_curve.is_mesh = is_mesh @@ -118,35 +126,20 @@ def export(self, ob, db_name): return rman_sg_curve - def _is_mesh(self, ob): - is_mesh = False - if len(ob.modifiers) > 0: - is_mesh = True - elif len(ob.data.splines) < 1: - is_mesh = True - elif ob.data.dimensions == '2D' and ob.data.fill_mode != 'NONE': - is_mesh = True - else: - l = ob.data.extrude + ob.data.bevel_depth - if l > 0: - is_mesh = True - - return is_mesh - def export_deform_sample(self, rman_sg_curve, ob, time_sample): if rman_sg_curve.is_mesh: super().export_deform_sample(rman_sg_curve, ob, time_sample, sg_node=rman_sg_curve.sg_mesh_node) - def export_object_primvars(self, ob, rman_sg_node): - if rman_sg_node.is_mesh: - super().export_object_primvars(ob, rman_sg_node, sg_node=rman_sg_node.sg_mesh_node) + #def export_object_primvars(self, ob, rman_sg_node): + # if rman_sg_node.is_mesh: + # super().export_object_primvars(ob, rman_sg_node, sg_node=rman_sg_node.sg_mesh_node) def update(self, ob, rman_sg_curve): for c in [ rman_sg_curve.sg_node.GetChild(i) for i in range(0, rman_sg_curve.sg_node.GetNumChildren())]: rman_sg_curve.sg_node.RemoveChild(c) self.rman_scene.sg_scene.DeleteDagNode(c) - rman_sg_curve.is_mesh = self._is_mesh(ob) + rman_sg_curve.is_mesh = object_utils.curve_is_mesh(ob) if rman_sg_curve.is_mesh: rman_sg_curve.sg_mesh_node = self.rman_scene.sg_scene.CreateMesh('%s-MESH' % rman_sg_curve.db_name) @@ -162,6 +155,13 @@ def update(self, ob, rman_sg_curve): else: self.update_curve(ob, rman_sg_curve) + def update_primvars(self, ob, primvar): + rm_scene = self.rman_scene.bl_scene.renderman + rm = ob.renderman + property_utils.set_primvar_bl_props(primvar, rm, inherit_node=rm_scene) + rm = ob.data.renderman + property_utils.set_primvar_bl_props(primvar, rm, inherit_node=rm_scene) + def update_bspline_curve(self, ob, rman_sg_curve): P, num_curves, nvertices, widths, index, name = get_bspline_curve(ob.data) num_pts = len(P) @@ -175,6 +175,9 @@ def update_bspline_curve(self, ob, rman_sg_curve): if widths: primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, widths, "vertex") primvar.SetIntegerDetail("index", index, "uniform") + #self.update_primvars(ob, primvar) + super().export_object_primvars(ob, primvar) + curves_sg.SetPrimVars(primvar) rman_sg_curve.sg_node.AddChild(curves_sg) @@ -210,6 +213,9 @@ def update_bezier_curve(self, ob, rman_sg_curve): primvar.SetIntegerDetail(self.rman_scene.rman.Tokens.Rix.k_Ri_nvertices, [num_pts], "uniform") if width: primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, width, "vertex") + + #self.update_primvars(ob, primvar) + super().export_object_primvars(ob, primvar) curves_sg.SetPrimVars(primvar) rman_sg_curve.sg_node.AddChild(curves_sg) \ No newline at end of file diff --git a/rman_translators/rman_dra_translator.py b/rman_translators/rman_dra_translator.py index 15be3d4b..1fade466 100644 --- a/rman_translators/rman_dra_translator.py +++ b/rman_translators/rman_dra_translator.py @@ -22,6 +22,10 @@ def export_deform_sample(self, rman_sg_dra, ob, time_sample): def update(self, ob, rman_sg_dra): rm = ob.renderman + if string_utils.check_frame_sensitive(rm.path_archive): + rman_sg_dra.is_frame_sensitive = True + else: + rman_sg_dra.is_frame_sensitive = False path_archive = string_utils.expand_string(rm.path_archive) bounds = (-100000, 100000, -100000, 100000, -100000, 100000 ) diff --git a/rman_translators/rman_emitter_translator.py b/rman_translators/rman_emitter_translator.py index 181db0b6..a07f3a04 100644 --- a/rman_translators/rman_emitter_translator.py +++ b/rman_translators/rman_emitter_translator.py @@ -71,6 +71,7 @@ def update(self, ob, psys, rman_sg_emitter): else: primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, width, "vertex") + super().export_object_primvars(ob, primvar) sg_emitter_node.SetPrimVars(primvar) # Attach material diff --git a/rman_translators/rman_empty_translator.py b/rman_translators/rman_empty_translator.py index 357a8c68..a080ac7f 100644 --- a/rman_translators/rman_empty_translator.py +++ b/rman_translators/rman_empty_translator.py @@ -10,9 +10,6 @@ class RmanEmptyTranslator(RmanTranslator): def __init__(self, rman_scene): super().__init__(rman_scene) - def export_object_primvars(self, ob, rman_sg_node): - pass - def update_transform(self, ob, rman_sg_group): pass @@ -22,6 +19,11 @@ def update_transform_sample(self, ob, rman_sg_group, index, seg): def update_transform_num_samples(self, rman_sg_group, motion_steps): pass + def clear_children(self, rman_sg_group): + if rman_sg_group.sg_node: + for c in [ rman_sg_group.sg_node.GetChild(i) for i in range(0, rman_sg_group.sg_node.GetNumChildren())]: + rman_sg_group.sg_node.RemoveChild(c) + def export(self, ob, db_name=""): sg_group = self.rman_scene.sg_scene.CreateGroup(db_name) rman_sg_group = RmanSgGroup(self.rman_scene, sg_group, db_name) diff --git a/rman_translators/rman_fluid_translator.py b/rman_translators/rman_fluid_translator.py index cff1e556..bfa8bd1c 100644 --- a/rman_translators/rman_fluid_translator.py +++ b/rman_translators/rman_fluid_translator.py @@ -6,6 +6,7 @@ from ..rfb_utils import scenegraph_utils from ..rfb_utils import particles_utils from ..rfb_utils import object_utils +from ..rfb_utils import mesh_utils from ..rfb_logger import rfb_log from mathutils import Matrix import bpy @@ -49,32 +50,6 @@ def export(self, ob, db_name): return rman_sg_fluid - def export_object_primvars(self, ob, rman_sg_node, sg_node=None): - if rman_sg_node.rman_sg_liquid_node: - sg_node = rman_sg_node.rman_sg_liquid_node - super().export_object_primvars(ob, rman_sg_node, sg_node=sg_node) - return - - sg_node = rman_sg_node.rman_sg_volume_node - super().export_object_primvars(ob, rman_sg_node, sg_node=sg_node) - prop_name = 'rman_micropolygonlength_volume' - rm = ob.renderman - rm_scene = self.rman_scene.bl_scene.renderman - meta = rm.prop_meta[prop_name] - val = getattr(rm, prop_name) - inherit_true_value = meta['inherit_true_value'] - if float(val) == inherit_true_value: - if hasattr(rm_scene, 'rman_micropolygonlength'): - val = getattr(rm_scene, 'rman_micropolygonlength') - - try: - primvars = sg_node.GetPrimVars() - primvars.SetFloat('dice:micropolygonlength', val) - sg_node.SetPrimVars(primvars) - except AttributeError: - rfb_log().debug("Cannot get RtPrimVar for this node") - - def export_deform_sample(self, rman_sg_fluid, ob, time_sample): return @@ -139,7 +114,7 @@ def update(self, ob, rman_sg_fluid): def update_fluid_mesh(self, ob, rman_sg_fluid, psys, fluid_data): sg_node = rman_sg_fluid.rman_sg_liquid_node mesh = ob.data - (nverts, verts, P, N) = object_utils._get_mesh_(mesh, get_normals=True) + (nverts, verts, P, N) = mesh_utils.get_mesh(mesh, get_normals=True) npolys = len(nverts) npoints = len(P) numnverts = len(verts) @@ -196,7 +171,7 @@ def update_fluid_particles(self, ob, rman_sg_fluid, psys, fluid_data): else: primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, P, "vertex") primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, width, "vertex") - + super().export_object_primvars(ob, primvar) sg_node.SetPrimVars(primvar) # Attach material @@ -218,12 +193,13 @@ def update_fluid_openvdb(self, ob, rman_sg_fluid, fluid_data): primvar = rman_sg_fluid.rman_sg_volume_node.GetPrimVars() primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_Ri_type, "blobbydso:impl_openvdb") - primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, transform_utils.convert_ob_bounds(ob.bound_box), 6) + primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, [-1e30, 1e30, -1e30, 1e30, -1e30, 1e30], 6) primvar.SetStringArray(self.rman_scene.rman.Tokens.Rix.k_blobbydso_stringargs, [cacheFile, "density:fogvolume"], 2) primvar.SetFloatDetail("density", [], "varying") primvar.SetFloatDetail("flame", [], "varying") - primvar.SetColorDetail("color", [], "varying") + primvar.SetColorDetail("color", [], "varying") + super().export_object_primvars(ob, primvar) rman_sg_fluid.rman_sg_volume_node.SetPrimVars(primvar) attrs = rman_sg_fluid.rman_sg_volume_node.GetAttributes() @@ -245,7 +221,7 @@ def update_fluid(self, ob, rman_sg_fluid, fluid_data): primvar.SetColorDetail("color", [item for index, item in enumerate(fluid_data.color_grid) if index % 4 != 0], "varying") primvar.SetVectorDetail("velocity", fluid_data.velocity_grid, "varying") primvar.SetFloatDetail("temperature", fluid_data.temperature_grid, "varying") - + super().export_object_primvars(ob, primvar) rman_sg_fluid.rman_sg_volume_node.SetPrimVars(primvar) attrs = rman_sg_fluid.rman_sg_volume_node.GetAttributes() diff --git a/rman_translators/rman_gpencil_translator.py b/rman_translators/rman_gpencil_translator.py index 456075d4..7f031ef2 100644 --- a/rman_translators/rman_gpencil_translator.py +++ b/rman_translators/rman_gpencil_translator.py @@ -20,11 +20,6 @@ def __init__(self, rman_scene): super().__init__(rman_scene) self.bl_type = 'GPENCIL' - - def export_object_primvars(self, ob, rman_sg_node): - pass - - def export(self, ob, db_name): prim_type = object_utils._detect_primitive_(ob) @@ -110,6 +105,7 @@ def _create_mesh(self, ob, i, lyr, stroke, rman_sg_gpencil, rman_sg_material, ad primvar.SetIntegerDetail(self.rman_scene.rman.Tokens.Rix.k_Ri_vertices, verts, "facevarying") if st: primvar.SetFloatArrayDetail("st", st, 2, "vertex") + super().export_object_primvars(ob, primvar) mesh_sg.SetPrimVars(primvar) if rman_sg_material: scenegraph_utils.set_material(mesh_sg, rman_sg_material.sg_fill_mat) @@ -145,6 +141,7 @@ def _create_points(self, ob, i, lyr, stroke, rman_sg_gpencil, rman_sg_material, primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, points, "vertex") primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, widths, "vertex") + super().export_object_primvars(ob, primvar) points_sg.SetPrimVars(primvar) # Attach material @@ -199,6 +196,7 @@ def _create_curve(self, ob, i, lyr, stroke, rman_sg_gpencil, rman_sg_material, a primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, widths, "vertex") + super().export_object_primvars(ob, primvar) curves_sg.SetPrimVars(primvar) # Attach material diff --git a/rman_translators/rman_hair_curves_translator.py b/rman_translators/rman_hair_curves_translator.py new file mode 100644 index 00000000..e3ec8bdc --- /dev/null +++ b/rman_translators/rman_hair_curves_translator.py @@ -0,0 +1,217 @@ +from .rman_translator import RmanTranslator +from ..rfb_utils import transform_utils +from ..rfb_utils import scenegraph_utils +from ..rfb_logger import rfb_log +from ..rman_sg_nodes.rman_sg_haircurves import RmanSgHairCurves +from mathutils import Vector +import math +import bpy +import numpy as np + +class BlHair: + + def __init__(self): + self.points = [] + self.vertsArray = [] + self.nverts = 0 + self.hair_width = [] + self.index = [] + self.bl_hair_attributes = [] + +class BlHairAttribute: + + def __init__(self): + self.rman_type = '' + self.rman_name = '' + self.rman_detail = None + self.values = [] + +class RmanHairCurvesTranslator(RmanTranslator): + + def __init__(self, rman_scene): + super().__init__(rman_scene) + self.bl_type = 'CURVES' + + def export(self, ob, db_name): + + sg_node = self.rman_scene.sg_scene.CreateGroup(db_name) + rman_sg_hair = RmanSgHairCurves(self.rman_scene, sg_node, db_name) + + return rman_sg_hair + + def clear_children(self, ob, rman_sg_hair): + if rman_sg_hair.sg_node: + for c in [ rman_sg_hair.sg_node.GetChild(i) for i in range(0, rman_sg_hair.sg_node.GetNumChildren())]: + rman_sg_hair.sg_node.RemoveChild(c) + self.rman_scene.sg_scene.DeleteDagNode(c) + rman_sg_hair.sg_curves_list.clear() + + def export_deform_sample(self, rman_sg_hair, ob, time_sample): + curves = self._get_strands_(ob) + for i, bl_curve in enumerate(curves): + curves_sg = rman_sg_hair.sg_curves_list[i] + if not curves_sg: + continue + primvar = curves_sg.GetPrimVars() + + primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, bl_curve.points, "vertex", time_sample) + curves_sg.SetPrimVars(primvar) + + def update(self, ob, rman_sg_hair): + if rman_sg_hair.sg_node: + if rman_sg_hair.sg_node.GetNumChildren() > 0: + self.clear_children(ob, rman_sg_hair) + + curves = self._get_strands_(ob) + if not curves: + return + + for i, bl_curve in enumerate(curves): + curves_sg = self.rman_scene.sg_scene.CreateCurves("%s-%d" % (rman_sg_hair.db_name, i)) + curves_sg.Define(self.rman_scene.rman.Tokens.Rix.k_cubic, "nonperiodic", "catmull-rom", len(bl_curve.vertsArray), len(bl_curve.points)) + primvar = curves_sg.GetPrimVars() + primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, bl_curve.points, "vertex") + + primvar.SetIntegerDetail(self.rman_scene.rman.Tokens.Rix.k_Ri_nvertices, bl_curve.vertsArray, "uniform") + index_nm = 'index' + primvar.SetIntegerDetail(index_nm, bl_curve.index, "uniform") + + width_detail = "vertex" + primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, bl_curve.hair_width, width_detail) + + for hair_attr in bl_curve.bl_hair_attributes: + if hair_attr.rman_detail is None: + continue + if hair_attr.rman_type == "float": + primvar.SetFloatDetail(hair_attr.rman_name, hair_attr.values, hair_attr.rman_detail) + elif hair_attr.rman_type == "float2": + primvar.SetFloatArrayDetail(hair_attr.rman_name, hair_attr.values, 2, hair_attr.rman_detail) + elif hair_attr.rman_type == "vector": + primvar.SetVectorDetail(hair_attr.rman_name, hair_attr.values, hair_attr.rman_detail) + elif hair_attr.rman_type == 'color': + primvar.SetColorDetail(hair_attr.rman_name, hair_attr.values, hair_attr.rman_detail) + elif hair_attr.rman_type == 'integer': + primvar.SetIntegerDetail(hair_attr.rman_name, hair_attr.values, hair_attr.rman_detail) + + curves_sg.SetPrimVars(primvar) + rman_sg_hair.sg_node.AddChild(curves_sg) + rman_sg_hair.sg_curves_list.append(curves_sg) + + def get_attributes(self, ob, bl_curve): + for attr in ob.data.attributes: + if attr.name in ['position']: + continue + hair_attr = None + if attr.data_type == 'FLOAT2': + hair_attr = BlHairAttribute() + hair_attr.rman_name = attr.name + hair_attr.rman_type = 'float2' + + npoints = len(attr.data) + values = np.zeros(npoints*2, dtype=np.float32) + attr.data.foreach_get('vector', values) + values = np.reshape(values, (npoints, 2)) + hair_attr.values = values.tolist() + + elif attr.data_type == 'FLOAT_VECTOR': + hair_attr = BlHairAttribute() + hair_attr.rman_name = attr.name + hair_attr.rman_type = 'vector' + + npoints = len(attr.data) + values = np.zeros(npoints*3, dtype=np.float32) + attr.data.foreach_get('vector', values) + values = np.reshape(values, (npoints, 3)) + hair_attr.values = values.tolist() + + elif attr.data_type in ['BYTE_COLOR', 'FLOAT_COLOR']: + hair_attr = BlHairAttribute() + hair_attr.rman_name = attr.name + if attr.name == 'color': + hair_attr.rman_name = 'Cs' + hair_attr.rman_type = 'color' + + npoints = len(attr.data) + values = np.zeros(npoints*4, dtype=np.float32) + attr.data.foreach_get('color', values) + values = np.reshape(values, (npoints, 4)) + hair_attr.values .extend(values[0:, 0:3].tolist()) + + elif attr.data_type == 'FLOAT': + hair_attr = BlHairAttribute() + hair_attr.rman_name = attr.name + hair_attr.rman_type = 'float' + hair_attr.array_len = -1 + + npoints = len(attr.data) + values = np.zeros(npoints, dtype=np.float32) + attr.data.foreach_get('value', values) + hair_attr.values = values.tolist() + elif attr.data_type in ['INT8', 'INT']: + hair_attr = BlHairAttribute() + hair_attr.rman_name = attr.name + hair_attr.rman_type = 'integer' + hair_attr.array_len = -1 + + npoints = len(attr.data) + values = np.zeros(npoints, dtype=np.int) + attr.data.foreach_get('value', values) + hair_attr.values = values.tolist() + + if hair_attr: + bl_curve.bl_hair_attributes.append(hair_attr) + if len(attr.data) == len(ob.data.curves): + hair_attr.rman_detail = 'uniform' + elif len(attr.data) == len(ob.data.points): + hair_attr.rman_detail = 'vertex' + + def _get_strands_(self, ob): + + curve_sets = [] + bl_curve = BlHair() + db = ob.data + for curve in db.curves: + if curve.points_length < 4: + continue + + npoints = len(curve.points) + strand_points = np.zeros(npoints*3, dtype=np.float32) + widths = np.zeros(npoints, dtype=np.float32) + curve.points.foreach_get('position', strand_points) + curve.points.foreach_get('radius', widths) + strand_points = np.reshape(strand_points, (npoints, 3)) + if np.count_nonzero(widths) == 0: + # radius is 0. Default to 0.005 + widths.fill(0.005) + widths = widths * 2 + strand_points = strand_points.tolist() + widths = widths.tolist() + + ''' + # do we really need to double the end points? + strand_points = strand_points[:1] + \ + strand_points + strand_points[-1:] + + widths = widths[:1] + widths + widths[-1:] + ''' + vertsInStrand = len(strand_points) + + bl_curve.points.extend(strand_points) + bl_curve.vertsArray.append(vertsInStrand) + bl_curve.hair_width.extend(widths) + bl_curve.index.append(curve.index) + bl_curve.nverts += vertsInStrand + + # if we get more than 100000 vertices, start a new BlHair. This + # is to avoid a maxint on the array length + if bl_curve.nverts > 100000: + self.get_attributes(ob, bl_curve) + curve_sets.append(bl_curve) + bl_curve = BlHair() + + if bl_curve.nverts > 0: + self.get_attributes(ob, bl_curve) + curve_sets.append(bl_curve) + + return curve_sets + \ No newline at end of file diff --git a/rman_translators/rman_hair_translator.py b/rman_translators/rman_hair_translator.py index 3745d3c5..0eb60447 100644 --- a/rman_translators/rman_hair_translator.py +++ b/rman_translators/rman_hair_translator.py @@ -1,5 +1,5 @@ from .rman_translator import RmanTranslator -from ..rfb_utils import object_utils +from ..rfb_utils import transform_utils from ..rfb_utils import scenegraph_utils from ..rfb_logger import rfb_log from ..rman_sg_nodes.rman_sg_hair import RmanSgHair @@ -7,8 +7,21 @@ import math import bpy import numpy as np - +class BlHair: + + def __init__(self): + self.points = [] + self.next_points = [] + self.vertsArray = [] + self.scalpST = [] + self.mcols = [] + self.nverts = 0 + self.hair_width = [] + + @property + def constant_width(self): + return (len(self.hair_width) < 2) class RmanHairTranslator(RmanTranslator): def __init__(self, rman_scene): @@ -30,16 +43,22 @@ def clear_children(self, ob, psys, rman_sg_hair): rman_sg_hair.sg_curves_list.clear() def export_deform_sample(self, rman_sg_hair, ob, psys, time_sample): + return + + ''' + # Keep this code below, in case we want to give users the option + # to do non-velocity based motion blur curves = self._get_strands_(ob, psys) - for i, (vertsArray, points, widths, scalpST) in enumerate(curves): + for i, bl_curve in enumerate(curves): curves_sg = rman_sg_hair.sg_curves_list[i] if not curves_sg: continue primvar = curves_sg.GetPrimVars() - primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, points, "vertex", time_sample) + primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, bl_curve.points, "vertex", time_sample) curves_sg.SetPrimVars(primvar) + ''' def update(self, ob, psys, rman_sg_hair): if rman_sg_hair.sg_node: @@ -50,32 +69,38 @@ def update(self, ob, psys, rman_sg_hair): if not curves: return - for i, (vertsArray, points, widths, scalpST, mcols) in enumerate(curves): + ob_inv_mtx = transform_utils.convert_matrix(ob.matrix_world.inverted_safe()) + for i, bl_curve in enumerate(curves): curves_sg = self.rman_scene.sg_scene.CreateCurves("%s-%d" % (rman_sg_hair.db_name, i)) - curves_sg.Define(self.rman_scene.rman.Tokens.Rix.k_cubic, "nonperiodic", "catmull-rom", len(vertsArray), len(points)) - primvar = curves_sg.GetPrimVars() + curves_sg.SetTransform(ob_inv_mtx) # puts points in object space + curves_sg.Define(self.rman_scene.rman.Tokens.Rix.k_cubic, "nonperiodic", "catmull-rom", len(bl_curve.vertsArray), len(bl_curve.points)) + primvar = curves_sg.GetPrimVars() + if rman_sg_hair.motion_steps: + super().set_primvar_times(rman_sg_hair.motion_steps, primvar) + + if self.rman_scene.do_motion_blur: + primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, bl_curve.points, "vertex", 0) + primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, bl_curve.next_points, "vertex", 1) + else: + primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, bl_curve.points, "vertex") - primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, points, "vertex") - primvar.SetIntegerDetail(self.rman_scene.rman.Tokens.Rix.k_Ri_nvertices, vertsArray, "uniform") + primvar.SetIntegerDetail(self.rman_scene.rman.Tokens.Rix.k_Ri_nvertices, bl_curve.vertsArray, "uniform") index_nm = psys.settings.renderman.hair_index_name if index_nm == '': index_nm = 'index' - primvar.SetIntegerDetail(index_nm, range(len(vertsArray)), "uniform") + primvar.SetIntegerDetail(index_nm, range(len(bl_curve.vertsArray)), "uniform") - width_detail = "constant" - if isinstance(widths, list): - width_detail = "vertex" - primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, widths, width_detail) + width_detail = "vertex" + if bl_curve.constant_width: + width_detail = "constant" + primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_width, bl_curve.hair_width, width_detail) - if len(scalpST): - primvar.SetFloatArrayDetail("scalpST", scalpST, 2, "uniform") + if len(bl_curve.scalpST): + primvar.SetFloatArrayDetail("scalpST", bl_curve.scalpST, 2, "uniform") - if len(mcols): - primvar.SetColorDetail("Cs", mcols, "uniform") + if len(bl_curve.mcols): + primvar.SetColorDetail("Cs", bl_curve.mcols, "uniform") - if rman_sg_hair.motion_steps: - super().set_primvar_times(rman_sg_hair.motion_steps, primvar) - curves_sg.SetPrimVars(primvar) rman_sg_hair.sg_node.AddChild(curves_sg) rman_sg_hair.sg_curves_list.append(curves_sg) @@ -113,19 +138,13 @@ def _get_strands_(self, ob, psys): tip_width = psys.settings.tip_radius * psys.settings.radius_scale base_width = psys.settings.root_radius * psys.settings.radius_scale - - conwidth = (tip_width == base_width) + conwidth = (tip_width == base_width) if self.rman_scene.is_interactive: steps = (2 ** psys.settings.display_step)+1 else: steps = (2 ** psys.settings.render_step)+1 - if conwidth: - hair_width = base_width - else: - hair_width = [] - num_parents = len(psys.particles) num_children = len(psys.child_particles) rm = psys.settings.renderman @@ -150,19 +169,19 @@ def _get_strands_(self, ob, psys): break curve_sets = [] - points = [] - vertsArray = [] - scalpST = [] - mcols = [] - nverts = 0 - - ob_inv_mtx = ob.matrix_world.inverted_safe() + bl_curve = BlHair() + if conwidth: + bl_curve.hair_width.append(base_width) + start_idx = 0 if psys.settings.child_type != 'NONE' and num_children > 0: start_idx = num_parents for pindex in range(start_idx, total_hair_count): + particle = psys.particles[ + (pindex - num_parents) % num_parents] strand_points = [] + next_strand_points = [] # walk through each strand for step in range(0, steps): pt = psys.co_hair(ob, particle_no=pindex, step=step) @@ -171,63 +190,57 @@ def _get_strands_(self, ob, psys): # this strand ends prematurely break - # put points in object space - pt = ob_inv_mtx @ pt - strand_points.append(pt) - if len(strand_points) > 1: - # double the first and last - strand_points = strand_points[:1] + \ - strand_points + strand_points[-1:] - vertsInStrand = len(strand_points) - - # catmull-rom requires at least 4 vertices - if vertsInStrand < 4: - continue - - # for varying width make the width array - if not conwidth: - decr = (base_width - tip_width) / (vertsInStrand - 2) - hair_width.extend([base_width] + [(base_width - decr * i) - for i in range(vertsInStrand - 2)] + - [tip_width]) - - # add the last point again - points.extend(strand_points) - vertsArray.append(vertsInStrand) - nverts += vertsInStrand - - # get the scalp ST - if export_st: - particle = psys.particles[ - (pindex - num_parents) % num_parents] - st = psys.uv_on_emitter(psys_modifier, particle=particle, particle_no=pindex, uv_no=uv_set) - scalpST.append([st[0], st[1]]) - - if export_mcol: - particle = psys.particles[ - (pindex - num_parents) % num_parents] - mcol = psys.mcol_on_emitter(psys_modifier, particle=particle, particle_no=pindex, vcol_no=mcol_set) - mcols.append([mcol[0], mcol[1], mcol[2]]) - - # if we get more than 100000 vertices, export ri.Curve and reset. This + if len(strand_points) < 2: + # not enought points + continue + + # double the first and last + strand_points = strand_points[:1] + \ + strand_points + strand_points[-1:] + + vertsInStrand = len(strand_points) + + # catmull-rom requires at least 4 vertices + if vertsInStrand < 4: + continue + + if self.rman_scene.do_motion_blur: + # calculate the points for the next frame using velocity + vel = Vector(particle.velocity / particle.lifetime ) + next_strand_points = [p + vel for p in strand_points] + + # for varying width make the width array + if not conwidth: + decr = (base_width - tip_width) / (vertsInStrand - 2) + bl_curve.hair_width.extend([base_width] + [(base_width - decr * i) + for i in range(vertsInStrand - 2)] + + [tip_width]) + + bl_curve.points.extend(strand_points) + bl_curve.next_points.extend(next_strand_points) + bl_curve.vertsArray.append(vertsInStrand) + bl_curve.nverts += vertsInStrand + + # get the scalp ST + if export_st: + st = psys.uv_on_emitter(psys_modifier, particle=particle, particle_no=pindex, uv_no=uv_set) + bl_curve.scalpST.append(st) + + # get mcol + if export_mcol: + mcol = psys.mcol_on_emitter(psys_modifier, particle=particle, particle_no=pindex, vcol_no=mcol_set) + bl_curve.mcols.append(mcol) + + # if we get more than 100000 vertices, start a new BlHair. This # is to avoid a maxint on the array length - if nverts > 100000: - curve_sets.append( - (vertsArray, points, hair_width, scalpST, mcols)) - - nverts = 0 - points = [] - vertsArray = [] - if not conwidth: - hair_width = [] - scalpST = [] - mcols = [] - - if nverts > 0: - curve_sets.append((vertsArray, points, - hair_width, scalpST, mcols)) + if bl_curve.nverts > 100000: + curve_sets.append(bl_curve) + bl_curve = BlHair() + + if bl_curve.nverts > 0: + curve_sets.append(bl_curve) return curve_sets \ No newline at end of file diff --git a/rman_translators/rman_light_translator.py b/rman_translators/rman_light_translator.py index 5e689072..dc95a39a 100644 --- a/rman_translators/rman_light_translator.py +++ b/rman_translators/rman_light_translator.py @@ -40,10 +40,7 @@ def __init__(self, rman_scene): super().__init__(rman_scene) self.bl_type = 'LIGHT' - def export_object_primvars(self, ob, rman_sg_node): - pass - - def export_object_attributes(self, ob, rman_sg_node): + def export_object_attributes(self, ob, rman_sg_node, remove=True): pass def export(self, ob, db_name): @@ -71,7 +68,7 @@ def update_light_attributes(self, ob, rman_sg_light): attrs.SetInteger("visibility:camera", int(primary_vis)) attrs.SetInteger("visibility:transmission", 0) attrs.SetInteger("visibility:indirect", 0) - obj_groups_str = "World,%s" % rman_sg_light.db_name + obj_groups_str = "World,%s" % string_utils.sanitize_node_name(ob.name_full) attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_grouping_membership, obj_groups_str) rman_sg_light.sg_node.SetAttributes(attrs) diff --git a/rman_translators/rman_lightfilter_translator.py b/rman_translators/rman_lightfilter_translator.py index 6c7ea7c9..0db705a2 100644 --- a/rman_translators/rman_lightfilter_translator.py +++ b/rman_translators/rman_lightfilter_translator.py @@ -11,10 +11,7 @@ def __init__(self, rman_scene): super().__init__(rman_scene) self.bl_type = 'LIGHTFILTER' - def export_object_primvars(self, ob, rman_sg_node): - pass - - def export_object_attributes(self, ob, rman_sg_node): + def export_object_attributes(self, ob, rman_sg_node, remove=True): pass def export_light_filters(self, ob, rman_sg_node, rm): @@ -33,7 +30,7 @@ def export_light_filters(self, ob, rman_sg_node, rm): light_filter_sg = None light_filter_db_name = object_utils.get_db_name(light_filter) - rman_sg_lightfilter = self.rman_scene.rman_objects.get(light_filter.original) + rman_sg_lightfilter = self.rman_scene.rman_prototypes.get(light_filter.original.data.original) if not rman_sg_lightfilter: rman_sg_lightfilter = self.export(light_filter, light_filter_db_name) elif not isinstance(rman_sg_lightfilter, RmanSgLightFilter): @@ -43,7 +40,7 @@ def export_light_filters(self, ob, rman_sg_node, rm): self.rman_scene.get_root_sg_node().RemoveChild(rman_sg_group.sg_node) rman_sg_lightfilter.instances.clear() del rman_sg_lightfilter - self.rman_scene.rman_objects.pop(light_filter.original) + self.rman_scene.rman_prototypes.pop(light_filter.original.data.original) rman_sg_lightfilter = self.export(light_filter, light_filter_db_name) self.update(light_filter, rman_sg_lightfilter) light_filters.append(rman_sg_lightfilter.sg_filter_node) @@ -88,12 +85,14 @@ def export(self, ob, db_name): rman_sg_lightfilter = RmanSgLightFilter(self.rman_scene, sg_group, db_name) rman_sg_lightfilter.sg_filter_node = sg_filter_node rman_sg_lightfilter.coord_sys = db_name + rman_sg_lightfilter.rman_type = 'LIGHTFILTER' rman_group_translator = self.rman_scene.rman_translators['GROUP'] rman_group_translator.update_transform(ob, rman_sg_lightfilter) self.rman_scene.get_root_sg_node().AddChild(rman_sg_lightfilter.sg_node) - self.rman_scene.rman_objects[ob.original] = rman_sg_lightfilter + proto_key = object_utils.prototype_key(ob) + self.rman_scene.rman_prototypes[proto_key] = rman_sg_lightfilter self.rman_scene.sg_scene.Root().AddCoordinateSystem(rman_sg_lightfilter.sg_node) return rman_sg_lightfilter @@ -105,7 +104,7 @@ def update(self, ob, rman_sg_lightfilter): rixparams.SetString("coordsys", rman_sg_lightfilter.coord_sys) # check if this light filter belongs to a light link - for ll in self.rman_scene.bl_scene.renderman.light_links: - if ll.light_ob == ob: - rixparams.SetString("linkingGroups", ob.name) - break \ No newline at end of file + if ob.original.data.renderman.linkingGroups != "": + rixparams.SetString("linkingGroups", ob.original.data.renderman.linkingGroups) + else: + rixparams.Remove("linkingGroups") \ No newline at end of file diff --git a/rman_translators/rman_material_translator.py b/rman_translators/rman_material_translator.py index af5fb7f3..e39618d8 100644 --- a/rman_translators/rman_material_translator.py +++ b/rman_translators/rman_material_translator.py @@ -13,6 +13,15 @@ import re import bpy +__MAP_CYCLES_PARAMS__ = { + "ShaderNodeTexNoise": { + "dimensions": "noise_dimensions" + }, + "ShaderNodeAttribute": { + "name": "attribute_name" + } +} + def get_root_node(node, type='bxdf'): rman_type = getattr(node, 'renderman_node_type', node.bl_idname) if rman_type == type: @@ -26,6 +35,13 @@ def get_root_node(node, type='bxdf'): return out return None +def get_cycles_value(node, param_name): + val = getattr(node, param_name, None) + if node.bl_idname in __MAP_CYCLES_PARAMS__: + params_map = __MAP_CYCLES_PARAMS__[node.bl_idname] + val = getattr(node, params_map.get(param_name, param_name), None) + return val + class RmanMaterialTranslator(RmanTranslator): def __init__(self, rman_scene): @@ -41,7 +57,6 @@ def export(self, mat, db_name): def update(self, mat, rman_sg_material, time_sample=0): - mat = mat.original rm = mat.renderman succeed = False @@ -115,8 +130,11 @@ def export_shader_nodetree(self, material, rman_sg_material, handle): return True # bxdf - socket = out.inputs['Bxdf'] - if socket.is_linked and len(socket.links) > 0: + socket = out.inputs.get('bxdf_in', None) + if socket is None: + # try old name + socket = out.inputs.get('Bxdf', None) + if socket and socket.is_linked and len(socket.links) > 0: from_node = socket.links[0].from_node linked_node = get_root_node(from_node, type='bxdf') if linked_node: @@ -142,10 +160,13 @@ def export_shader_nodetree(self, material, rman_sg_material, handle): self.create_pxrdiffuse_node(rman_sg_material, handle) # light - socket = out.inputs['Light'] - if socket.is_linked and len(socket.links) > 0: + socket = out.inputs.get('light_in', None) + if socket is None: + # try old name + socket = out.inputs.get('Light', None) + if socket and socket.is_linked and len(socket.links) > 0: from_node = socket.links[0].from_node - linked_node = get_root_node(from_node, type='light') + linked_node = get_root_node(socket.links[0].from_node, type='light') if linked_node: lightNodesList = [] sub_nodes = [] @@ -164,8 +185,11 @@ def export_shader_nodetree(self, material, rman_sg_material, handle): rman_sg_material.sg_node.SetLight(lightNodesList) # displacement - socket = out.inputs['Displacement'] - if socket.is_linked and len(socket.links) > 0: + socket = out.inputs.get('displace_in', None) + if socket is None: + # use old name + socket = out.inputs.get('Displacement', None) + if socket and socket.is_linked and len(socket.links) > 0: from_node = socket.links[0].from_node linked_node = get_root_node(from_node, type='displace') if linked_node: @@ -271,7 +295,8 @@ def export_simple_shader(self, mat, rman_sg_material, mat_handle=''): bxdf_name = '%s_PxrDisneyBsdf' % name sg_node = self.rman_scene.rman.SGManager.RixSGShader("Bxdf", "PxrDisneyBsdf", bxdf_name) rix_params = sg_node.params - rix_params.SetColor('baseColor', string_utils.convert_val(mat.diffuse_color, 'color')) + diffuse_color = string_utils.convert_val(mat.diffuse_color, type_hint='color') + rix_params.SetColor('baseColor', diffuse_color) rix_params.SetFloat('metallic', mat.metallic ) rix_params.SetFloat('roughness', mat.roughness) rix_params.SetFloat('specReflectScale', mat.metallic ) @@ -360,12 +385,11 @@ def translate_cycles_node(self, mat, rman_sg_material, node, mat_name, group_nod param_name = node_desc_param._name if param_name in node.inputs: continue - if param_name == 'name': - param_name = 'attribute_name' - if not hasattr(node, param_name): - continue param_type = node_desc_param.type - val = string_utils.convert_val(getattr(node, param_name), + val = get_cycles_value(node, param_name) + if val is None: + continue + val = string_utils.convert_val(val, type_hint=param_type) if param_type == 'string': diff --git a/rman_translators/rman_mesh_translator.py b/rman_translators/rman_mesh_translator.py index ff647a3c..ea3c6042 100644 --- a/rman_translators/rman_mesh_translator.py +++ b/rman_translators/rman_mesh_translator.py @@ -1,6 +1,7 @@ from .rman_translator import RmanTranslator from ..rman_sg_nodes.rman_sg_mesh import RmanSgMesh from ..rfb_utils import object_utils +from ..rfb_utils import mesh_utils from ..rfb_utils import string_utils from ..rfb_utils import property_utils from ..rfb_utils import scenegraph_utils @@ -8,6 +9,7 @@ import bpy import math +import bmesh import numpy as np def _get_mats_faces_(nverts, material_ids): @@ -46,8 +48,7 @@ def _get_mesh_uv_(mesh, name=""): uv_count = len(uv_loop_layer.data) fastuvs = np.zeros(uv_count * 2) - uv_loop_layer.data.foreach_get("uv", fastuvs) - fastuvs = fastuvs.reshape(uv_count, 2) + uv_loop_layer.data.foreach_get("uv", fastuvs) uvs = fastuvs.tolist() return uvs @@ -149,17 +150,6 @@ def _export_reference_pose(ob, rm, rixparams, vertex_detail): rixparams.SetNormalDetail('__WNref', rman__WNref, 'vertex') else: rfb_log().error("Number of WNref primvars do not match. Please re-freeze the reference position.") - - ''' - if rman__Pref: - rixparams.SetPointDetail('__Pref', rman__Pref, 'vertex') - if rman__WPref: - rixparams.SetPointDetail('__WPref', rman__WPref, 'vertex') - if rman__Nref: - rixparams.SetNormalDetail('__Nref', rman__Nref, 'vertex') - if rman__WNref: - rixparams.SetNormalDetail('__WNref', rman__WNref, 'vertex') - ''' def export_tangents(ob, geo, rixparams, uvmap="", name=""): # also export the tangent and bitangent vectors @@ -200,9 +190,10 @@ def _get_primvars_(ob, rman_sg_mesh, geo, rixparams): if rm.export_default_uv: uvs = _get_mesh_uv_(geo) if uvs and len(uvs) > 0: - detail = "facevarying" if facevarying_detail == len(uvs) else "vertex" + detail = "facevarying" if (facevarying_detail*2) == len(uvs) else "vertex" rixparams.SetFloatArrayDetail("st", uvs, 2, detail) - export_tangents(ob, geo, rixparams) + if rm.export_default_tangents: + export_tangents(ob, geo, rixparams) if rm.export_default_vcol: vcols = _get_mesh_vcol_(geo) @@ -226,7 +217,7 @@ def _get_primvars_(ob, rman_sg_mesh, geo, rixparams): elif p.data_source == 'UV_TEXTURE': uvs = _get_mesh_uv_(geo, p.data_name) if uvs and len(uvs) > 0: - detail = "facevarying" if facevarying_detail == len(uvs) else "vertex" + detail = "facevarying" if (facevarying_detail*2) == len(uvs) else "vertex" rixparams.SetFloatArrayDetail(p.name, uvs, 2, detail) if p.export_tangents: export_tangents(ob, geo, rixparams, uvmap=p.data_name, name=p.name) @@ -243,27 +234,7 @@ def _get_primvars_(ob, rman_sg_mesh, geo, rixparams): rixparams.SetColorDetail(p.data_name, vattr, detail) rm_scene = rman_sg_mesh.rman_scene.bl_scene.renderman - for prop_name, meta in rm.prop_meta.items(): - if 'primvar' not in meta: - continue - - val = getattr(rm, prop_name) - if not val: - continue - - if 'inheritable' in meta: - if float(val) == meta['inherit_true_value']: - if hasattr(rm_scene, prop_name): - val = getattr(rm_scene, prop_name) - - ri_name = meta['primvar'] - is_array = False - array_len = -1 - if 'arraySize' in meta: - is_array = True - array_len = meta['arraySize'] - param_type = meta['renderman_type'] - property_utils.set_rix_param(rixparams, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=rm) + property_utils.set_primvar_bl_props(rixparams, rm, inherit_node=rm_scene) class RmanMeshTranslator(RmanTranslator): @@ -272,16 +243,7 @@ def __init__(self, rman_scene): self.bl_type = 'MESH' def _get_subd_tags_(self, ob, mesh, primvar): - creases = [] - - # only do creases 1 edge at a time for now, - # detecting chains might be tricky.. - for e in mesh.edges: - if e.crease > 0.0: - creases.append((e.vertices[0], e.vertices[1], - e.crease * e.crease * 10)) - # squared, to match blender appareance better - #: range 0 - 10 (infinitely sharp) + rm = mesh.renderman tags = ['interpolateboundary', 'facevaryinginterpolateboundary'] nargs = [1, 0, 0, 1, 0, 0] @@ -290,12 +252,54 @@ def _get_subd_tags_(self, ob, mesh, primvar): floatargs = [] stringargs = [] - if len(creases) > 0: - for c in creases: - tags.append('crease') - nargs.extend([2, 1, 0]) - intargs.extend([c[0], c[1]]) - floatargs.append(c[2]) + # get creases + edges_len = len(mesh.edges) + creases = np.zeros(edges_len, dtype=np.float32) + mesh.edges.foreach_get('crease', creases) + if (creases > 0.0).any(): + # we have edges where their crease is > 0.0 + # grab only those edges + crease_edges = np.zeros(edges_len*2, dtype=np.int) + mesh.edges.foreach_get('vertices', crease_edges) + crease_edges = np.reshape(crease_edges, (edges_len, 2)) + crease_edges = crease_edges[creases > 0.0] + + # squared, to match blender appareance better + #: range 0 - 10 (infinitely sharp) + creases = creases * creases * 10.0 + + creases = creases[creases > 0.0] + edges_subset_len = len(creases) + + tags.extend(['crease'] * edges_subset_len) + nargs.extend([2, 1, 0] * edges_subset_len) + intargs.extend(crease_edges.flatten().tolist()) + floatargs.extend(creases.tolist()) + + + holes_facemap = getattr(rm, 'rman_holesFaceMap', '') + if holes_facemap != '' and holes_facemap in ob.face_maps: + # use this facemap for face edit holes + holes_idx = ob.face_maps[holes_facemap].index + bm = bmesh.new() + bm.from_mesh(mesh) + fm = bm.faces.layers.face_map.verify() + + holes = [] + for face in bm.faces: + face_idx = face.index + map_idx = face[fm] + if map_idx == holes_idx: + holes.append(face_idx) + if holes: + tags.append('faceedit') + num_holes = len(holes) + for h in holes: + intargs.extend([1, h]) + nargs.extend([num_holes*2, 0, num_holes]) + stringargs.extend(['hole'] * num_holes) + + bm.free() primvar.SetStringArray(self.rman_scene.rman.Tokens.Rix.k_Ri_subdivtags, tags, len(tags)) primvar.SetIntegerArray(self.rman_scene.rman.Tokens.Rix.k_Ri_subdivtagnargs, nargs, len(nargs)) @@ -305,8 +309,10 @@ def _get_subd_tags_(self, ob, mesh, primvar): def export(self, ob, db_name): - sg_node = self.rman_scene.sg_scene.CreateMesh(db_name) + sg_node = self.rman_scene.sg_scene.CreateGroup(db_name) rman_sg_mesh = RmanSgMesh(self.rman_scene, sg_node, db_name) + rman_sg_mesh.sg_mesh = self.rman_scene.sg_scene.CreateMesh('') + rman_sg_mesh.sg_node.AddChild(rman_sg_mesh.sg_mesh) if self.rman_scene.do_motion_blur: rman_sg_mesh.is_transforming = object_utils.is_transforming(ob) @@ -319,9 +325,9 @@ def export_deform_sample(self, rman_sg_mesh, ob, time_sample, sg_node=None): mesh = None mesh = ob.to_mesh() if not sg_node: - sg_node = rman_sg_mesh.sg_node + sg_node = rman_sg_mesh.sg_mesh primvar = sg_node.GetPrimVars() - P = object_utils._get_mesh_points_(mesh) + P = mesh_utils.get_mesh_points_(mesh) npoints = len(P) if rman_sg_mesh.npoints != npoints: @@ -348,6 +354,19 @@ def export_deform_sample(self, rman_sg_mesh, ob, time_sample, sg_node=None): ob.to_mesh_clear() + def update_primvar(self, ob, rman_sg_mesh, prop_name): + mesh = ob.to_mesh() + primvars = rman_sg_mesh.sg_node.GetPrimVars() + if prop_name in mesh.renderman.prop_meta: + rm = mesh.renderman + meta = rm.prop_meta[prop_name] + rm_scene = self.rman_scene.bl_scene.renderman + property_utils.set_primvar_bl_prop(primvars, prop_name, meta, rm, inherit_node=rm_scene) + else: + super().update_object_primvar(ob, primvars, prop_name) + rman_sg_mesh.sg_node.SetPrimVars(primvars) + ob.to_mesh_clear() + def update(self, ob, rman_sg_mesh, input_mesh=None, sg_node=None): rm = ob.renderman mesh = input_mesh @@ -357,12 +376,12 @@ def update(self, ob, rman_sg_mesh, input_mesh=None, sg_node=None): return True if not sg_node: - sg_node = rman_sg_mesh.sg_node + sg_node = rman_sg_mesh.sg_mesh rman_sg_mesh.is_subdiv = object_utils.is_subdmesh(ob) use_smooth_normals = getattr(ob.data.renderman, 'rman_smoothnormals', False) get_normals = (rman_sg_mesh.is_subdiv == 0 and not use_smooth_normals) - (nverts, verts, P, N) = object_utils._get_mesh_(mesh, get_normals=get_normals) + (nverts, verts, P, N) = mesh_utils.get_mesh(mesh, get_normals=get_normals) # if this is empty continue: if nverts == []: @@ -373,7 +392,13 @@ def update(self, ob, rman_sg_mesh, input_mesh=None, sg_node=None): rman_sg_mesh.nverts = 0 rman_sg_mesh.is_transforming = False rman_sg_mesh.is_deforming = False + rman_sg_mesh.sg_node.RemoveChild(rman_sg_mesh.sg_mesh) return None + + # double check that sg_mesh has been added + # as a child + if rman_sg_mesh.sg_node.GetNumChildren() < 1: + rman_sg_mesh.sg_node.AddChild(rman_sg_mesh.sg_mesh) npolys = len(nverts) npoints = len(P) @@ -416,6 +441,8 @@ def update(self, ob, rman_sg_mesh, input_mesh=None, sg_node=None): subdiv_scheme = getattr(ob.data.renderman, 'rman_subdiv_scheme', 'none') rman_sg_mesh.subdiv_scheme = subdiv_scheme + super().export_object_primvars(ob, primvar) + if rman_sg_mesh.is_multi_material: material_ids = _get_material_ids(ob, mesh) for mat_id, faces in \ @@ -440,8 +467,6 @@ def update(self, ob, rman_sg_mesh, input_mesh=None, sg_node=None): pvars.Inherit(primvar) pvars.SetIntegerArray(self.rman_scene.rman.Tokens.Rix.k_shade_faceset, faces, len(faces)) sg_sub_mesh.SetPrimVars(pvars) - # call export_object_primvars so we can get things like displacement bound - super().export_object_primvars(ob, rman_sg_mesh, sg_sub_mesh) scenegraph_utils.set_material(sg_sub_mesh, sg_material.sg_node) sg_node.AddChild(sg_sub_mesh) rman_sg_mesh.multi_material_children.append(sg_sub_mesh) diff --git a/rman_translators/rman_nurbs_translator.py b/rman_translators/rman_nurbs_translator.py index 7d801b9d..7e2db3df 100644 --- a/rman_translators/rman_nurbs_translator.py +++ b/rman_translators/rman_nurbs_translator.py @@ -260,5 +260,5 @@ def update(self, ob, rman_sg_nurbs): primvar.SetHpointDetail(self.rman_scene.rman.Tokens.Rix.k_Pw, P, "vertex") primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_uknot, uknots, len(uknots)) primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_vknot, vknots, len(vknots)) - + super().export_object_primvars(ob, primvar) rman_sg_nurbs.sg_node.SetPrimVars(primvar) diff --git a/rman_translators/rman_openvdb_translator.py b/rman_translators/rman_openvdb_translator.py index cd8a7109..196022b4 100644 --- a/rman_translators/rman_openvdb_translator.py +++ b/rman_translators/rman_openvdb_translator.py @@ -4,11 +4,9 @@ from ..rfb_utils import transform_utils from ..rfb_utils import string_utils from ..rfb_utils import scenegraph_utils -from ..rfb_utils import texture_utils -from ..rfb_utils.envconfig_utils import envconfig +from ..rfb_utils import property_utils from ..rfb_logger import rfb_log import json -import os class RmanOpenVDBTranslator(RmanTranslator): def __init__(self, rman_scene): @@ -23,34 +21,21 @@ def export(self, ob, db_name): return rman_sg_openvdb - def export_object_primvars(self, ob, rman_sg_node, sg_node=None): - if not sg_node: - sg_node = rman_sg_node.sg_node - - if not sg_node: - return - - super().export_object_primvars(ob, rman_sg_node, sg_node=sg_node) - prop_name = 'rman_micropolygonlength_volume' - rm = ob.renderman - rm_scene = self.rman_scene.bl_scene.renderman - meta = rm.prop_meta[prop_name] - val = getattr(rm, prop_name) - inherit_true_value = meta['inherit_true_value'] - if float(val) == inherit_true_value: - if hasattr(rm_scene, 'rman_micropolygonlength'): - val = getattr(rm_scene, 'rman_micropolygonlength') - - try: - primvars = sg_node.GetPrimVars() - primvars.SetFloat('dice:micropolygonlength', val) - sg_node.SetPrimVars(primvars) - except AttributeError: - rfb_log().debug("Cannot get RtPrimVar for this node") - def export_deform_sample(self, rman_sg_openvdb, ob, time_sample): pass + def update_primvar(self, ob, rman_sg_openvdb, prop_name): + db = ob.data + primvars = rman_sg_openvdb.sg_node.GetPrimVars() + if prop_name in db.renderman.prop_meta: + rm = db.renderman + meta = rm.prop_meta[prop_name] + rm_scene = self.rman_scene.bl_scene.renderman + property_utils.set_primvar_bl_prop(primvars, prop_name, meta, rm, inherit_node=rm_scene) + else: + super().update_object_primvar(ob, primvars, prop_name) + rman_sg_openvdb.sg_node.SetPrimVars(primvars) + def update(self, ob, rman_sg_openvdb): db = ob.data rm = db.renderman @@ -58,9 +43,9 @@ def update(self, ob, rman_sg_openvdb): primvar = rman_sg_openvdb.sg_node.GetPrimVars() primvar.Clear() bounds = transform_utils.convert_ob_bounds(ob.bound_box) - primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, string_utils.convert_val(bounds), 6) if db.filepath == '': primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_Ri_type, "box") + primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, string_utils.convert_val(bounds), 6) rman_sg_openvdb.sg_node.SetPrimVars(primvar) return @@ -69,24 +54,36 @@ def update(self, ob, rman_sg_openvdb): if not grids.load(): rfb_log().error("Could not load grids and metadata for volume: %s" % ob.name) primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_Ri_type, "box") + primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, string_utils.convert_val(bounds), 6) rman_sg_openvdb.sg_node.SetPrimVars(primvar) return + if len(grids) < 1: + rfb_log().error("Grids length=0: %s" % ob.name) + primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_Ri_type, "box") + primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, string_utils.convert_val(bounds), 6) + rman_sg_openvdb.sg_node.SetPrimVars(primvar) + return + active_index = grids.active_index active_grid = grids[active_index] if active_grid.data_type not in ['FLOAT', 'DOUBLE']: rfb_log().error("Active grid is not of float type: %s" % ob.name) primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_Ri_type, "box") + primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, string_utils.convert_val(bounds), 6) rman_sg_openvdb.sg_node.SetPrimVars(primvar) return + + primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, [-1e30, 1e30, -1e30, 1e30, -1e30, 1e30], 6) openvdb_file = filepath_utils.get_real_path(db.filepath) if db.is_sequence: # if we have a sequence, get the current frame filepath from the grids - openvdb_file = filepath_utils.get_real_path(grids.frame_filepath) - - #openvdb_file = texture_utils.get_txmanager().get_output_vdb(ob) + openvdb_file = filepath_utils.get_real_path(grids.frame_filepath) + rman_sg_openvdb.is_frame_sensitive = True + else: + rman_sg_openvdb.is_frame_sensitive = False openvdb_attrs = dict() openvdb_attrs['filterWidth'] = getattr(rm, 'openvdb_filterwidth') @@ -117,4 +114,6 @@ def update(self, ob, rman_sg_openvdb): scenegraph_utils.export_vol_aggregate(self.rman_scene.bl_scene, primvar, ob) primvar.SetInteger("volume:dsominmax", rm.volume_dsominmax) + primvar.SetInteger("volume:dsovelocity", rm.volume_dsovelocity) + super().export_object_primvars(ob, primvar) rman_sg_openvdb.sg_node.SetPrimVars(primvar) \ No newline at end of file diff --git a/rman_translators/rman_points_translator.py b/rman_translators/rman_points_translator.py index 2b322dff..92946ec6 100644 --- a/rman_translators/rman_points_translator.py +++ b/rman_translators/rman_points_translator.py @@ -1,10 +1,6 @@ from .rman_translator import RmanTranslator from ..rman_sg_nodes.rman_sg_points import RmanSgPoints -from ..rfb_utils import object_utils -from ..rfb_utils import string_utils - -import bpy -import math +from ..rfb_utils import mesh_utils class RmanPointsTranslator(RmanTranslator): @@ -23,7 +19,7 @@ def export_deform_sample(self, rman_sg_points, ob, time_sample): mesh = None mesh = ob.to_mesh() - P = object_utils._get_mesh_points_(mesh) + P = mesh_utils.get_mesh_points_(mesh) primvar = rman_sg_points.sg_node.GetPrimVars() npoints = len(P) @@ -47,7 +43,7 @@ def update(self, ob, rman_sg_points, input_mesh=None): if not mesh: mesh = ob.to_mesh() - P = object_utils._get_mesh_points_(mesh) + P = mesh_utils.get_mesh_points_(mesh) # if this is empty continue: if not P or len(P) < 1: @@ -69,7 +65,7 @@ def update(self, ob, rman_sg_points, input_mesh=None): primvar.SetPointDetail(self.rman_scene.rman.Tokens.Rix.k_P, P, "vertex") primvar.SetFloatDetail(self.rman_scene.rman.Tokens.Rix.k_constantwidth, rm.primitive_point_width, "constant") - + super().export_object_primvars(ob, primvar) rman_sg_points.sg_node.SetPrimVars(primvar) if not input_mesh: diff --git a/rman_translators/rman_procedural_translator.py b/rman_translators/rman_procedural_translator.py index 4d1facab..d20f7b97 100644 --- a/rman_translators/rman_procedural_translator.py +++ b/rman_translators/rman_procedural_translator.py @@ -28,5 +28,5 @@ def update(self, ob, rman_sg_procedural): primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_dsoname, path_dso) primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_data, rm.path_dso_initial_data ) primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_bound, bounds, 6) - + super().export_object_primvars(ob, primvar) rman_sg_procedural.sg_node.SetPrimVars(primvar) diff --git a/rman_translators/rman_quadric_translator.py b/rman_translators/rman_quadric_translator.py index 80605474..be848d4d 100644 --- a/rman_translators/rman_quadric_translator.py +++ b/rman_translators/rman_quadric_translator.py @@ -1,5 +1,6 @@ from .rman_translator import RmanTranslator from ..rman_sg_nodes.rman_sg_quadric import RmanSgQuadric +from ..rfb_utils import object_utils class RmanQuadricTranslator(RmanTranslator): @@ -12,6 +13,9 @@ def export(self, ob, db_name): sg_node = self.rman_scene.sg_scene.CreateQuadric(db_name) rman_sg_quadric = RmanSgQuadric(self.rman_scene, sg_node, db_name) + if self.rman_scene.do_motion_blur: + rman_sg_quadric.is_transforming = object_utils.is_transforming(ob) + return rman_sg_quadric def export_deform_sample(self, rman_sg_quadric, ob, time_sample): @@ -58,4 +62,5 @@ def update(self, ob, rman_sg_quadric): primvar.SetFloat(self.rman_scene.rman.Tokens.Rix.k_Ri_phimax, rm.quadric_phimax) primvar.SetFloat(self.rman_scene.rman.Tokens.Rix.k_Ri_thetamax, rm.quadric_sweepangle) + super().export_object_primvars(ob, primvar) rman_sg_quadric.sg_node.SetPrimVars(primvar) diff --git a/rman_translators/rman_runprogram_translator.py b/rman_translators/rman_runprogram_translator.py index bfcffd25..9e069dab 100644 --- a/rman_translators/rman_runprogram_translator.py +++ b/rman_translators/rman_runprogram_translator.py @@ -25,8 +25,8 @@ def update(self, ob, rman_sg_runprogram): bounds = (-100000, 100000, -100000, 100000, -100000, 100000 ) primvar = rman_sg_runprogram.sg_node.GetPrimVars() - primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_filename, runprogram_path) + primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_filename, path_runprogram) primvar.SetString(self.rman_scene.rman.Tokens.Rix.k_data, rm.runprogram_args) primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_bound, bounds, 6) - + super().export_object_primvars(ob, primvar) rman_sg_runprogram.sg_node.SetPrimVars(primvar) diff --git a/rman_translators/rman_translator.py b/rman_translators/rman_translator.py index e9007a6a..a940d465 100644 --- a/rman_translators/rman_translator.py +++ b/rman_translators/rman_translator.py @@ -68,59 +68,58 @@ def export_transform(self, ob, sg_node): v = transform_utils.convert_matrix(m) - sg_node.SetTransform( v ) + sg_node.SetTransform( v ) + + def export_object_primvars(self, ob, primvars): + ''' + This method should be called by subclasses of RmanTranslator + in their update() methods, if they are setting any primvars. This + sets things like displacement bound and micropolygon length. + + Args: + ob (bpy.types.Object) - Blender Object + primvars (RtPrimVars) - primitive variables + ''' + rm = ob.renderman + rm_scene = self.rman_scene.bl_scene.renderman + property_utils.set_primvar_bl_props(primvars, rm, inherit_node=rm_scene) + + def update_primvar(self, ob, rman_sg_node, prop_name): + pass - def export_object_primvars(self, ob, rman_sg_node, sg_node=None): - if not sg_node: - sg_node = rman_sg_node.sg_node + def update_object_primvar(self, ob, primvars, prop_name): + ''' + This method should be called by subclasses of RmanTranslator + in their update_primvar() methods. - if not sg_node: - return + Args: + ob (bpy.types.Object) - Blender Object + primvars (RtPrimVars) - primitive variables + prop_name (str) - name of the Blender property that was updated + ''' rm = ob.renderman rm_scene = self.rman_scene.bl_scene.renderman - try: - primvars = sg_node.GetPrimVars() - except AttributeError: - rfb_log().debug("Cannot get RtPrimVar for this node") - return + meta = rm.prop_meta[prop_name] + property_utils.set_primvar_bl_prop(primvars, prop_name, meta, rm, inherit_node=rm_scene) - # set any properties marked primvar in the config file - for prop_name, meta in rm.prop_meta.items(): - if 'primvar' not in meta: - continue - - val = getattr(rm, prop_name) - if not val: - continue - - if 'inheritable' in meta: - if float(val) == meta['inherit_true_value']: - if hasattr(rm_scene, prop_name): - val = getattr(rm_scene, prop_name) - - ri_name = meta['primvar'] - is_array = False - array_len = -1 - if 'arraySize' in meta: - is_array = True - array_len = meta['arraySize'] - param_type = meta['renderman_type'] - property_utils.set_rix_param(primvars, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len) - - sg_node.SetPrimVars(primvars) - - def export_object_id(self, ob, rman_sg_node, ob_inst): + def export_instance_attributes(self, ob, rman_sg_node, ob_inst): + ''' + Export attributes that should vary between each instance + ''' if not rman_sg_node.sg_node: return name = ob.name_full + is_instance = ob_inst.is_instance + if is_instance: + name = ob_inst.parent.name attrs = rman_sg_node.sg_node.GetAttributes() rman_type = object_utils._detect_primitive_(ob) # Add ID if name != "": - persistent_id = ob_inst.persistent_id[0] + persistent_id = ob_inst.persistent_id[1] if persistent_id == 0: - persistent_id = int(hashlib.sha1(ob.name_full.encode()).hexdigest(), 16) % 10**8 + persistent_id = int(hashlib.sha1(name.encode()).hexdigest(), 16) % 10**8 self.rman_scene.obj_hash[persistent_id] = name attrs.SetInteger(self.rman_scene.rman.Tokens.Rix.k_identifier_id, persistent_id) @@ -132,19 +131,25 @@ def export_object_id(self, ob, rman_sg_node, ob_inst): ]: id = int(hashlib.sha1(rman_sg_node.db_name.encode()).hexdigest(), 16) % 10**8 procprimid = float(id) - attrs.SetFloat('user:procprimid', procprimid) + attrs.SetFloat('user:procprimid', procprimid) + + if is_instance: + attrs.SetFloat('user:blender_is_instance', 1) + attrs.SetFloatArray('user:blender_instance_uv', ob_inst.uv, 2) + else: + attrs.SetFloat('user:blender_is_instance', 0) rman_sg_node.sg_node.SetAttributes(attrs) def export_light_linking_attributes(self, ob, attrs): rm = ob.renderman + bl_scene = self.rman_scene.bl_scene + all_lightfilters = [string_utils.sanitize_node_name(l.name) for l in scene_utils.get_all_lightfilters(bl_scene)] if self.rman_scene.bl_scene.renderman.invert_light_linking: lighting_subset = [] lightfilter_subset = [] - bl_scene = self.rman_scene.bl_scene all_lights = [string_utils.sanitize_node_name(l.name) for l in scene_utils.get_all_lights(bl_scene, include_light_filters=False)] - all_lightfilters = [string_utils.sanitize_node_name(l.name) for l in scene_utils.get_all_lightfilters(bl_scene)] for ll in self.rman_scene.bl_scene.renderman.light_links: light_ob = ll.light_ob light_props = shadergraph_utils.get_rman_light_properties_group(light_ob) @@ -168,38 +173,13 @@ def export_light_linking_attributes(self, ob, attrs): if lighting_subset: lighting_subset = lighting_subset + all_lights # include all other lights that are not linked - attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lighting_subset, ' '. join(lighting_subset) ) + attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lighting_subset, ','. join(lighting_subset) ) if lightfilter_subset: lightfilter_subset = lightfilter_subset + all_lightfilters - attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lightfilter_subset, ' ' . join(lightfilter_subset)) + attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lightfilter_subset, ',' . join(lightfilter_subset)) - else: - exclude_subset = [] - lightfilter_subset = [] - for subset in rm.rman_lighting_excludesubset: - if subset.light_ob is None: - continue - nm = string_utils.sanitize_node_name(subset.light_ob.name) - exclude_subset.append(nm) - - for subset in rm.rman_lightfilter_subset: - if subset.light_ob is None: - continue - nm = string_utils.sanitize_node_name(subset.light_ob.name) - lightfilter_subset.append(nm) - - if exclude_subset: - attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lighting_excludesubset, ' '. join(exclude_subset) ) - else: - attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lighting_excludesubset, '') - - if lightfilter_subset: - attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lightfilter_subset, ' ' . join(lightfilter_subset)) - else: - attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lightfilter_subset, '') - - def export_object_attributes(self, ob, rman_sg_node): + def export_object_attributes(self, ob, rman_sg_node, remove=True): if not rman_sg_node.sg_node: return @@ -209,38 +189,14 @@ def export_object_attributes(self, ob, rman_sg_node): # set any properties marked riattr in the config file for prop_name, meta in rm.prop_meta.items(): - if 'riattr' not in meta: - continue - - ri_name = meta['riattr'] - val = getattr(rm, prop_name) - if 'inheritable' in meta: - cond = meta['inherit_true_value'] - if isinstance(cond, str): - node = rm - if exec(cond): - attrs.Remove(ri_name) - continue - elif float(val) == cond: - attrs.Remove(ri_name) - continue - - is_array = False - array_len = -1 - if 'arraySize' in meta: - is_array = True - array_len = meta['arraySize'] - if type(val) == str and val.startswith('['): - val = eval(val) - param_type = meta['renderman_type'] - property_utils.set_rix_param(attrs, param_type, ri_name, val, is_reference=False, is_array=is_array, array_len=array_len) + property_utils.set_riattr_bl_prop(attrs, prop_name, meta, rm, check_inherit=True, remove=remove) obj_groups_str = "World" obj_groups_str += "," + name lpe_groups_str = "*" for obj_group in self.rman_scene.bl_scene.renderman.object_groups: for member in obj_group.members: - if member.ob_pointer == ob: + if member.ob_pointer.original == ob.original: obj_groups_str += ',' + obj_group.name lpe_groups_str += ',' + obj_group.name break @@ -266,7 +222,6 @@ def export_object_attributes(self, ob, rman_sg_node): if tokens[1] != '': filePath = tokens[0] filePath = string_utils.expand_string(filePath, - frame=self.rman_scene.bl_frame_current, asFilePath=True) attrs.SetString('user:bake_filename_attr', filePath) diff --git a/rman_translators/rman_volume_translator.py b/rman_translators/rman_volume_translator.py index 1e8a7fb1..dc374f88 100644 --- a/rman_translators/rman_volume_translator.py +++ b/rman_translators/rman_volume_translator.py @@ -2,6 +2,7 @@ from ..rman_sg_nodes.rman_sg_volume import RmanSgVolume from ..rfb_utils import scenegraph_utils from ..rfb_utils import transform_utils +from ..rfb_utils import property_utils from ..rfb_logger import rfb_log import bpy @@ -19,35 +20,14 @@ def export(self, ob, db_name): return rman_sg_volume - def export_object_primvars(self, ob, rman_sg_node, sg_node=None): - if not sg_node: - sg_node = rman_sg_node.sg_node - - if not sg_node: - return - - super().export_object_primvars(ob, rman_sg_node, sg_node=sg_node) - prop_name = 'rman_micropolygonlength_volume' - rm = ob.renderman - rm_scene = self.rman_scene.bl_scene.renderman - meta = rm.prop_meta[prop_name] - val = getattr(rm, prop_name) - inherit_true_value = meta['inherit_true_value'] - if float(val) == inherit_true_value: - if hasattr(rm_scene, 'rman_micropolygonlength'): - val = getattr(rm_scene, 'rman_micropolygonlength') - - try: - primvars = sg_node.GetPrimVars() - primvars.SetFloat('dice:micropolygonlength', val) - sg_node.SetPrimVars(primvars) - except AttributeError: - rfb_log().debug("Cannot get RtPrimVar for this node") - - def export_deform_sample(self, rman_sg_volume, ob, time_sample): pass + def update_primvar(self, ob, rman_sg_volume, prop_name): + primvars = rman_sg_volume.sg_node.GetPrimVars() + super().update_object_primvar(ob, primvars, prop_name) + rman_sg_volume.sg_node.SetPrimVars(primvars) + def update(self, ob, rman_sg_volume): rman_sg_volume.sg_node.Define(0,0,0) primvar = rman_sg_volume.sg_node.GetPrimVars() @@ -59,6 +39,7 @@ def update(self, ob, rman_sg_volume): primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, bound_box, 6) else: primvar.SetFloatArray(self.rman_scene.rman.Tokens.Rix.k_Ri_Bound, transform_utils.convert_ob_bounds(ob.bound_box), 6) + super().export_object_primvars(ob, primvar) rman_sg_volume.sg_node.SetPrimVars(primvar) attrs = rman_sg_volume.sg_node.GetAttributes() diff --git a/rman_ui/rfb_qt.py b/rman_ui/rfb_qt.py index e842d96e..1c50d157 100644 --- a/rman_ui/rfb_qt.py +++ b/rman_ui/rfb_qt.py @@ -47,6 +47,156 @@ """ +## CSS copied from $RMANTREE/bin/rman_utils/rman_assets/common/ui_style.py +__rmanPltF__ = {'bg': (68, 68, 68), + 'darkbg': (43, 43, 43), + 'alternatebg': (53, 53, 53), + 'lightbg': (78, 78, 78), + 'tipbg': (58, 58, 58), + 'tiptext': (192, 192, 192), + 'text': (200, 200, 200), + 'textselected': (225, 225, 225), + 'orange': (229, 154, 0), + 'blue': (118, 149, 229), + 'bluehover': (81, 95, 125), + 'handle': (93, 93, 93)} + +__BASE_CSS__ = ''' + QWidget { + background: %(bg)s; + } + QPushButton { + border-radius: 2px; + color: %(text)s; + background-color: #5D5D5D; + min-height: 18px; + margin-left: 5px; + margin-right: 5px; + margin-top: 1px; + padding-left: 3px; + padding-right: 3px; + } + QPushButton:hover { + background-color: #5D5D5D; + color: %(textselected)s; + } + QPushButton:pressed { + background-color: rgba(32, 64, 128, 255); + color: %(textselected)s; + } + QFrame { + background-color: %(darkbg)s; + border-width: 2px; + border-radius: 4px; + margin: 0px; + } + QLabel { + background: %(bg)s; + color: %(text)s; + } + QGroupBox { + background: %(bg)s; + color: %(text)s; + } + QSplitter { + border-style: none; + background-color: %(bg)s; + } + QSplitter::handle { + background-color: %(bg)s; + } + QSplitter::handle:hover { + background-color: %(bluehover)s; + } + QMenuBar { + border-width: 0px; + border-image: none; + color: %(text)s; + } + QMenuBar::item { + color: %(text)s; + background-color: %(bg)s; + } + QMenuBar::item::selected { + background-color: %(bg)s; + color: %(textselected)s; + } + QMenu { + background-color: %(bg)s; + color: %(text)s; + } + QMenu::item::selected { + background-color: %(bg)s; + color: %(textselected)s; + } + QToolTip { + background-color: %(tipbg)s; + color: %(tiptext)s; + border: 3px solid %(bluehover)s; + border-radius: 3px; + padding: 4px; + } + QProgressBar { + border: 1px solid %(bg)s; + } + QProgressBar::chunk { + background-color: %(blue)s; + } + QLineEdit { + background-color: %(darkbg)s; + background-image: none; + color: %(text)s; + } + QHeaderView { + background-color: %(darkbg)s; + border-color: %(darkbg)s; + } + QHeaderView::section { + background-color: %(darkbg)s; + background-image: none; + border-image: none; + border-color: %(darkbg)s; + color: %(blue)s; + font-weight: bold; + } + QTreeWidget { + margin: 0px; + padding: 0px; + border-width: 2px; + border-radius: 4px; + border-color: %(darkbg)s; + color: %(text)s; + background-color: %(darkbg)s; + alternate-background-color: %(alternatebg)s; + min-width: 138px; + } + QTreeView { + margin: 0px; + padding: 0px; + border-width: 2px; + border-radius: 4px; + border-color: %(darkbg)s; + color: %(text)s; + background-color: %(darkbg)s; + alternate-background-color: %(alternatebg)s; + min-width: 138px; + } + QListWidget { + margin: 0px; + padding: 0px; + border-width: 2px; + border-radius: 4px; + border-color: %(darkbg)s; + color: %(text)s; + background-color: %(darkbg)s; + alternate-background-color: %(alternatebg)s; + min-width: 138px; + } + QDoubleSpinBox { + color: %(text)s; + } +''' + class RfbBaseQtAppTimed(bpy.types.Operator): """Run a Qt app inside of Blender, without blocking Blender.""" @@ -57,6 +207,9 @@ class RfbBaseQtAppTimed(bpy.types.Operator): def __init__(self): self._app = (QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)) + + # always use the Fusion style + self._app.setStyle("Fusion") def modal(self, context, event): """Run modal.""" @@ -70,6 +223,18 @@ def modal(self, context, event): def execute(self, context): """Process the event loop of the Qt app.""" + + # explicitly set the style sheet + # we don't seem to be inheriting the style sheet correctly + # from the children widgets + sh = self._window.styleSheet() + plt = dict(__rmanPltF__) + for nm, rgb in plt.items(): + plt[nm] = 'rgb(%d, %d, %d)' % (rgb[0], rgb[1], rgb[2]) + css = __BASE_CSS__ % plt + sh += css + self._app.setStyleSheet(sh) + self._window.show() wm = context.window_manager # Run every 0.01 seconds @@ -88,7 +253,17 @@ class RmanQtWrapper(QtWidgets.QDialog): def __init__(self): super().__init__() - self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + if sys.platform == "darwin": + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + else: + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + + bg_role = self.backgroundRole() + plt = self.palette() + bg_color = plt.color(bg_role) + bg_color.setRgb(__rmanPltF__['bg'][0], __rmanPltF__['bg'][1], __rmanPltF__['bg'][2]) + plt.setColor(bg_role, bg_color) + self.setPalette(plt) def closeEvent(self, event): event.accept() @@ -98,6 +273,7 @@ def process_qt_events(app, window): if window and not window.isVisible(): return None app.processEvents() + window.update() return 0.01 # Run again after 0.001 seconds def run_with_timer(window, cls): @@ -106,4 +282,5 @@ def run_with_timer(window, cls): if not window: window = cls() window.show() - bpy.app.timers.register(functools.partial(process_qt_events, app, window)) \ No newline at end of file + bpy.app.timers.register(functools.partial(process_qt_events, app, window)) + return window \ No newline at end of file diff --git a/rman_ui/rman_ui_aovs.py b/rman_ui/rman_ui_aovs.py index 60d2613f..83bd9a0d 100644 --- a/rman_ui/rman_ui_aovs.py +++ b/rman_ui/rman_ui_aovs.py @@ -256,9 +256,10 @@ def draw_item(self, layout, context, item): # denoise options row = col.row() row.prop(item, 'denoise') - row = col.row() - row.enabled = item.denoise - row.prop(item, 'denoise_mode') + if rm.use_legacy_denoiser: + row = col.row() + row.enabled = item.denoise + row.prop(item, 'denoise_mode') row = col.row() row.label(text='') @@ -311,18 +312,18 @@ def draw_item(self, layout, context, item): col = layout.column() col.label(text="Remap Settings") row = col.row(align=True) - row.prop(channel, "remap_a", text="A") - row.prop(channel, "remap_b", text="B") - row.prop(channel, "remap_c", text="C") + row.prop(channel, "remap_a") + row.prop(channel, "remap_b") + row.prop(channel, "remap_c") + layout.separator() + row = col.row() + row.prop(channel, "chan_pixelfilter") + row = col.row() + if channel.chan_pixelfilter != 'default': + row.prop(channel, "chan_pixelfilter_x", text="Size X") + row.prop(channel, "chan_pixelfilter_y", text="Size Y") layout.separator() - if rm.hider_pixelFilterMode != 'importance': - row = col.row() - row.prop(channel, "chan_pixelfilter") - row = col.row() - if channel.chan_pixelfilter != 'default': - row.prop(channel, "chan_pixelfilter_x", text="Size X") - row.prop(channel, "chan_pixelfilter_y", text="Size Y") - layout.separator() + row = col.row() row.prop(channel, "stats_type") layout.separator() @@ -419,7 +420,8 @@ class PRMAN_OT_revert_renderman_aovs(bpy.types.Operator): def execute(self, context): active_layer = context.view_layer rm_rl = active_layer.renderman - rm_rl.custom_aovs.clear() + rm_rl.custom_aovs.clear() + rm_rl.dspy_channels.clear() rm_rl.use_renderman = False return {'FINISHED'} diff --git a/rman_ui/rman_ui_camera_panels.py b/rman_ui/rman_ui_camera_panels.py index 72f5a559..627e604d 100644 --- a/rman_ui/rman_ui_camera_panels.py +++ b/rman_ui/rman_ui_camera_panels.py @@ -55,7 +55,7 @@ def draw(self, context): split = layout.split(factor=0.35) - split.label(text=socket.name + ':') + split.label(text=socket.identifier + ':') split.context_pointer_set("socket", socket) split.context_pointer_set("node", output) diff --git a/rman_ui/rman_ui_header_panels.py b/rman_ui/rman_ui_header_panels.py index c358ddda..d9f592c1 100644 --- a/rman_ui/rman_ui_header_panels.py +++ b/rman_ui/rman_ui_header_panels.py @@ -38,6 +38,11 @@ def poll(cls, context): rd = context.scene.render return rd.engine == 'PRMAN_RENDER' + def add_arrange_op(self, layout): + rman_icon = rfb_icons.get_icon('rman_graph') + layout.operator('node.button', icon_value=rman_icon.icon_id) + layout.operator('node.na_align_nodes', icon_value=rman_icon.icon_id) + def draw(self, context): layout = self.layout @@ -66,13 +71,18 @@ def draw(self, context): if rman_output_node.solo_node_name != '': op = layout.operator('node.rman_set_node_solo', text='Reset Solo', icon='FILE_REFRESH') - op.refresh_solo = True + op.refresh_solo = True + + self.add_arrange_op(layout) elif type(context.space_data.id) == bpy.types.World: if not context.space_data.id.renderman.use_renderman_node: layout.operator( - 'material.rman_add_rman_nodetree', text="Add RenderMan Nodes").idtype = "world" - + 'material.rman_add_rman_nodetree', text="Add RenderMan Nodes").idtype = "world" + else: + self.add_arrange_op(layout) + else: + self.add_arrange_op(layout) class NODE_HT_DrawRenderHeaderNode(bpy.types.Header): ''' diff --git a/rman_ui/rman_ui_light_handlers/__init__.py b/rman_ui/rman_ui_light_handlers/__init__.py index b5b24986..2a1e1943 100644 --- a/rman_ui/rman_ui_light_handlers/__init__.py +++ b/rman_ui/rman_ui_light_handlers/__init__.py @@ -3,7 +3,6 @@ import bgl import os from gpu_extras.batch import batch_for_shader -from ...rfb_utils import texture_utils from ...rfb_utils import string_utils from ...rfb_utils import prefs_utils from ...rfb_logger import rfb_log @@ -447,6 +446,10 @@ (10, 20, 21) ] +__MTX_Y_180__ = Matrix.Rotation(math.radians(180.0), 4, 'Y') +__MTX_X_90__ = Matrix.Rotation(math.radians(90.0), 4, 'X') +__MTX_Y_90__ = Matrix.Rotation(math.radians(90.0), 4, 'Y') + _VERTEX_SHADER_UV_ = ''' uniform mat4 modelMatrix; uniform mat4 viewProjectionMatrix; @@ -533,8 +536,8 @@ def _get_sun_direction(ob): rm = light.renderman.get_light_node() m = Matrix.Identity(4) - m = m @ Matrix.Rotation(math.radians(90.0), 4, 'X') - m = m @ Matrix.Rotation(math.radians(90.0), 4, 'Y') + m = m @ __MTX_X_90__ + m = m @ __MTX_Y_90__ month = float(rm.month) day = float(rm.day) @@ -799,7 +802,7 @@ def draw_rect_light(ob): set_selection_color(ob) ob_matrix = Matrix(ob.matrix_world) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = ob_matrix @ __MTX_Y_180__ box = [m @ Vector(pt) for pt in s_rmanLightLogo['box']] box_indices = _get_indices(s_rmanLightLogo['box']) @@ -840,18 +843,14 @@ def draw_rect_light(ob): indices = _FRUSTUM_DRAW_HELPER_.idx_buffer(len(pts), 0, 0) draw_line_shape(ob, _SHADER_, pts, indices) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') - tex = '' - col = None if light_shader_name == 'PxrRectLight': + m = ob_matrix @ __MTX_Y_180__ tex = light_shader.lightColorMap col = light_shader.lightColor - elif light_shader_name in ['PxrGoboLightFilter', 'PxrCookieLightFilter']: - tex = light_shader.map - pts = ((0.5, -0.5, 0.0), (-0.5, -0.5, 0.0), (-0.5, 0.5, 0.0), (0.5, 0.5, 0.0)) - uvs = ((1, 1), (0, 1), (0, 0), (1, 0)) - draw_solid(ob, pts, m, uvs=uvs, tex=tex, col=col) + pts = ((0.5, -0.5, 0.0), (-0.5, -0.5, 0.0), (-0.5, 0.5, 0.0), (0.5, 0.5, 0.0)) + uvs = ((1, 1), (0, 1), (0, 0), (1, 0)) + draw_solid(ob, pts, m, uvs=uvs, tex=tex, col=col) def draw_sphere_light(ob): global _FRUSTUM_DRAW_HELPER_ @@ -861,18 +860,18 @@ def draw_sphere_light(ob): set_selection_color(ob) ob_matrix = Matrix(ob.matrix_world) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = ob_matrix @ __MTX_Y_180__ disk = [m @ Vector(pt) for pt in s_diskLight] disk_indices = _get_indices(s_diskLight) draw_line_shape(ob, _SHADER_, disk, disk_indices) - m2 = m @ Matrix.Rotation(math.radians(90.0), 4, 'Y') + m2 = m @ __MTX_Y_90__ disk = [m2 @ Vector(pt) for pt in s_diskLight] disk_indices = _get_indices(s_diskLight) draw_line_shape(ob, _SHADER_, disk, disk_indices) - m3 = m @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m3 = m @ __MTX_X_90__ disk = [m3 @ Vector(pt) for pt in s_diskLight] disk_indices = _get_indices(s_diskLight) draw_line_shape(ob, _SHADER_, disk, disk_indices) @@ -908,7 +907,7 @@ def draw_sphere_light(ob): indices = _FRUSTUM_DRAW_HELPER_.idx_buffer(len(pts), 0, 0) draw_line_shape(ob, _SHADER_, pts, indices) - m = ob_matrix @ Matrix.Scale(0.5, 4) @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m = ob_matrix @ Matrix.Scale(0.5, 4) @ __MTX_X_90__ idx_buffer = make_sphere_idx_buffer() if light_shader_name in ['PxrSphereLight']: col = light_shader.lightColor @@ -931,7 +930,7 @@ def draw_envday_light(ob): ob_matrix = m m = Matrix(ob_matrix) - m = m @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m = m @ __MTX_X_90__ west_rr_shape = [m @ Vector(pt) for pt in s_envday['west_rr_shape']] west_rr_indices = _get_indices(s_envday['west_rr_shape']) @@ -1006,7 +1005,7 @@ def draw_disk_light(ob): set_selection_color(ob) ob_matrix = Matrix(ob.matrix_world) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = ob_matrix @ __MTX_Y_180__ disk = [m @ Vector(pt) for pt in s_diskLight] disk_indices = _get_indices(s_diskLight) @@ -1047,7 +1046,7 @@ def draw_disk_light(ob): indices = _FRUSTUM_DRAW_HELPER_.idx_buffer(len(pts), 0, 0) draw_line_shape(ob, _SHADER_, pts, indices) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = ob_matrix @ __MTX_Y_180__ col = light_shader.lightColor draw_solid(ob, s_diskLight, m, col=col) @@ -1058,7 +1057,7 @@ def draw_dist_light(ob): set_selection_color(ob) ob_matrix = Matrix(ob.matrix_world) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = ob_matrix @ __MTX_Y_180__ arrow1 = [m @ Vector(pt) for pt in s_distantLight['arrow1']] arrow1_indices = _get_indices(s_distantLight['arrow1']) @@ -1097,12 +1096,12 @@ def draw_portal_light(ob): R_inside_indices = _get_indices(s_rmanLightLogo['R_inside']) draw_line_shape(ob, _SHADER_, R_inside, R_inside_indices) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = ob_matrix @ __MTX_Y_180__ arrow = [m @ Vector(pt) for pt in s_rmanLightLogo['arrow']] arrow_indices = _get_indices(s_rmanLightLogo['arrow']) draw_line_shape(ob, _SHADER_, arrow, arrow_indices) - m = ob_matrix @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m = ob_matrix @ __MTX_X_90__ m = m @ Matrix.Scale(0.5, 4) rays = [m @ Vector(pt) for pt in s_portalRays] rays_indices = _get_indices(s_portalRays) @@ -1118,7 +1117,7 @@ def draw_dome_light(ob): scale = max(sca) # take the max axis m = Matrix.Rotation(angle, 4, axis) m = m @ Matrix.Scale(100 * scale, 4) - m = m @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m = m @ __MTX_X_90__ sphere_pts = make_sphere() sphere = [m @ Vector(p) for p in sphere_pts] @@ -1204,13 +1203,13 @@ def draw_rounded_rectangles(ob, left, right, b = radius+bottomEdge draw_arc(a, b, 10, 3, right, -bottom, pts) - translate = m #Matrix.Translation( Vector([0,0, zOffset1])) @ m + translate = m shape_pts = [translate @ Vector(pt) for pt in pts] shape_pts_indices = _get_indices(shape_pts) draw_line_shape(ob, _SHADER_, shape_pts, shape_pts_indices) - translate = m #Matrix.Translation( Vector([0,0, zOffset2])) @ m + translate = m shape_pts = [translate @ Vector(pt) for pt in pts] shape_pts_indices = _get_indices(shape_pts) @@ -1235,7 +1234,7 @@ def draw_rod(ob, leftEdge, rightEdge, topEdge, bottomEdge, topEdge, bottomEdge, front, -back, m) - m = world_mat @ Matrix.Rotation(math.radians(-90.0), 4, 'X') + m = world_mat @ __MTX_X_90__ # top and bottom @@ -1244,7 +1243,7 @@ def draw_rod(ob, leftEdge, rightEdge, topEdge, bottomEdge, leftEdge, rightEdge, backEdge, frontEdge, top, -bottom, m) - m = world_mat @ Matrix.Rotation(math.radians(90.0), 4, 'Y') + m = world_mat @ __MTX_Y_90__ # left and right @@ -1258,7 +1257,7 @@ def draw_rod_light_filter(ob): set_selection_color(ob) m = Matrix(ob.matrix_world) - m = m @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = m @ __MTX_Y_180__ light = ob.data rm = light.renderman.get_light_node() @@ -1350,7 +1349,7 @@ def draw_ramp_light_filter(ob): set_selection_color(ob) m = Matrix(ob.matrix_world) - m = m @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = m @ __MTX_Y_180__ # begin begin_m = m @ Matrix.Scale(begin, 4) @@ -1359,11 +1358,11 @@ def draw_ramp_light_filter(ob): disk_indices = _get_indices(s_diskLight) draw_line_shape(ob, _SHADER_, disk, disk_indices) - m2 = begin_m @ Matrix.Rotation(math.radians(90.0), 4, 'Y') + m2 = begin_m @ __MTX_Y_90__ disk = [m2 @ Vector(pt) for pt in s_diskLight] draw_line_shape(ob, _SHADER_, disk, disk_indices) - m3 = begin_m @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m3 = begin_m @ __MTX_X_90__ disk = [m3 @ Vector(pt) for pt in s_diskLight] draw_line_shape(ob, _SHADER_, disk, disk_indices) @@ -1373,11 +1372,11 @@ def draw_ramp_light_filter(ob): disk = [end_m @ Vector(pt) for pt in s_diskLight] draw_line_shape(ob, _SHADER_, disk, disk_indices) - m2 = end_m @ Matrix.Rotation(math.radians(90.0), 4, 'Y') + m2 = end_m @ __MTX_Y_90__ disk = [m2 @ Vector(pt) for pt in s_diskLight] draw_line_shape(ob, _SHADER_, disk, disk_indices) - m3 = end_m @ Matrix.Rotation(math.radians(90.0), 4, 'X') + m3 = end_m @ __MTX_X_90__ disk = [m3 @ Vector(pt) for pt in s_diskLight] draw_line_shape(ob, _SHADER_, disk, disk_indices) @@ -1385,7 +1384,7 @@ def draw_ramp_light_filter(ob): elif rampType == 1: m = Matrix(ob.matrix_world) - m = m @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = m @ __MTX_Y_180__ box = [m @ Vector(pt) for pt in s_rmanLightLogo['box']] n = mathutils.geometry.normal(box) @@ -1410,7 +1409,7 @@ def draw_ramp_light_filter(ob): set_selection_color(ob) m = Matrix(ob.matrix_world) - m = m @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = m @ __MTX_Y_180__ disk_indices = _get_indices(s_diskLight) if begin > 0.0: @@ -1426,17 +1425,20 @@ def draw_ramp_light_filter(ob): else: pass -def draw_barn_light_filter(ob): +def draw_barn_light_filter(ob, light_shader, light_shader_name): global _BARN_LIGHT_DRAW_HELPER_ _SHADER_.bind() m = Matrix(ob.matrix_world) - m = m @ Matrix.Rotation(math.radians(180.0), 4, 'Y') + m = m @ __MTX_Y_180__ - set_selection_color(ob) + set_selection_color(ob) - _BARN_LIGHT_DRAW_HELPER_.update_input_params(ob) + radius = 1.0 + if light_shader_name in ['PxrGoboLightFilter', 'PxrCookieLightFilter']: + radius = 0.0 + _BARN_LIGHT_DRAW_HELPER_.update_input_params(ob, radius) vtx_buffer = _BARN_LIGHT_DRAW_HELPER_.vtx_buffer() pts = [m @ Vector(pt) for pt in vtx_buffer ] @@ -1446,6 +1448,20 @@ def draw_barn_light_filter(ob): draw_line_shape(ob, _SHADER_, pts, indices) + if light_shader_name in ['PxrGoboLightFilter', 'PxrCookieLightFilter']: + col = light_shader.fillColor + tex = light_shader.map + w = light_shader.width + h = light_shader.height + invertU = int(getattr(light_shader, 'invertU', False)) + invertV = int(getattr(light_shader, 'invertV', False)) + u = 1.0 - invertU + v = 1.0 - invertV + pts = ((0.5*w, -0.5*h, 0.0), (-0.5*w, -0.5*h, 0.0), (-0.5*w, 0.5*h, 0.0), (0.5*w, 0.5*h, 0.0)) + #uvs = ((0, 1), (1,1), (1, 0), (0,0)) + uvs = ((1.0-u, v), (u,v), (u, 1.0-v), (1.0-u, 1.0-v)) + draw_solid(ob, pts, m, uvs=uvs, tex=tex, col=col) + def draw(): global _PRMAN_TEX_CACHE_ global _RMAN_TEXTURED_LIGHTS_ @@ -1506,15 +1522,15 @@ def draw(): draw_dome_light(ob) elif light_shader_name == 'PxrCylinderLight': draw_cylinder_light(ob) - elif light_shader_name in ['PxrGoboLightFilter', 'PxrCookieLightFilter', 'PxrRectLight']: + elif light_shader_name in ['PxrRectLight']: draw_rect_light(ob) elif light_shader_name in ['PxrRodLightFilter', 'PxrBlockerLightFilter']: draw_rod_light_filter(ob) elif light_shader_name == 'PxrRampLightFilter': draw_ramp_light_filter(ob) - elif light_shader_name == 'PxrBarnLightFilter': + elif light_shader_name in ['PxrGoboLightFilter', 'PxrCookieLightFilter', 'PxrBarnLightFilter']: # get all lights that the barn is attached to - draw_barn_light_filter(ob) + draw_barn_light_filter(ob, light_shader, light_shader_name) else: draw_sphere_light(ob) diff --git a/rman_ui/rman_ui_light_handlers/barn_light_filter_draw_helper.py b/rman_ui/rman_ui_light_handlers/barn_light_filter_draw_helper.py index 74a3bfe6..af6464cc 100644 --- a/rman_ui/rman_ui_light_handlers/barn_light_filter_draw_helper.py +++ b/rman_ui/rman_ui_light_handlers/barn_light_filter_draw_helper.py @@ -95,7 +95,7 @@ def __init__(self): self.invert = 0 self.depth = 10.0 - def update_input_params(self, obj): + def update_input_params(self, obj, radius): self.parents = get_parented_lights(obj) self.num_lights = len(self.parents) @@ -111,7 +111,7 @@ def update_input_params(self, obj): self.useLightDirection = getattr(rm, "useLightDirection", 0) self.width = getattr(rm, "width", 1.0) self.height = getattr(rm, "height", 1.0) - self.radius = getattr(rm, "radius", 1.0) + self.radius = getattr(rm, "radius", radius) self.edge = getattr(rm, "edge", 0.0) self.scaleWidth = getattr(rm, "scaleWidth", 1.0) self.scaleHeight = getattr(rm, "scaleHeight", 1.0) diff --git a/rman_ui/rman_ui_material_panels.py b/rman_ui/rman_ui_material_panels.py index 99b41900..c729599f 100644 --- a/rman_ui/rman_ui_material_panels.py +++ b/rman_ui/rman_ui_material_panels.py @@ -159,14 +159,15 @@ def draw(self, context): col = split.column() layout.separator() - if not rman_output_node.inputs['Bxdf'].is_linked: + input_name = 'bxdf_in' + if not rman_output_node.inputs[input_name].is_linked: panel_node_draw(layout, context, mat, 'RendermanOutputNode', 'Bxdf') elif not filter_parameters or filter_method == 'NONE': panel_node_draw(layout, context, mat, 'RendermanOutputNode', 'Bxdf') elif filter_method == 'STICKY': - bxdf_node = rman_output_node.inputs['Bxdf'].links[0].from_node + bxdf_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(bxdf_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -175,7 +176,7 @@ def draw(self, context): expr = rman_output_node.bxdf_match_expression if expr == '': return - bxdf_node = rman_output_node.inputs['Bxdf'].links[0].from_node + bxdf_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(bxdf_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -271,14 +272,15 @@ def draw(self, context): col = split.column() col = split.column() - if not rman_output_node.inputs['Light'].is_linked: + input_name = 'light_in' + if not rman_output_node.inputs[input_name].is_linked: draw_nodes_properties_ui( - layout, context, nt, input_name=self.shader_type) + layout, context, nt, input_name=input_name) elif not filter_parameters or filter_method == 'NONE': draw_nodes_properties_ui( - layout, context, nt, input_name=self.shader_type) + layout, context, nt, input_name=input_name) elif filter_method == 'STICKY': - light_node = rman_output_node.inputs['Light'].links[0].from_node + light_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(light_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -287,7 +289,7 @@ def draw(self, context): expr = rman_output_node.light_match_expression if expr == '': return - light_node = rman_output_node.inputs['Light'].links[0].from_node + light_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(light_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -295,7 +297,7 @@ def draw(self, context): prop_names, context, nt) else: draw_nodes_properties_ui( - layout, context, nt, input_name=self.shader_type) + layout, context, nt, input_name=input_name) class MATERIAL_PT_renderman_shader_displacement(ShaderPanel, Panel): bl_context = "material" @@ -336,14 +338,15 @@ def draw(self, context): col = split.column() col = split.column() - if not rman_output_node.inputs['Displacement'].is_linked: + input_name = 'displace_in' + if not rman_output_node.inputs[input_name].is_linked: draw_nodes_properties_ui( - layout, context, nt, input_name=self.shader_type) + layout, context, nt, input_name=input_name) elif not filter_parameters or filter_method == 'NONE': draw_nodes_properties_ui( - layout, context, nt, input_name=self.shader_type) + layout, context, nt, input_name=input_name) elif filter_method == 'STICKY': - disp_node = rman_output_node.inputs['Displacement'].links[0].from_node + disp_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(disp_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -352,7 +355,7 @@ def draw(self, context): expr = rman_output_node.disp_match_expression if expr == '': return - disp_node = rman_output_node.inputs['Displacement'].links[0].from_node + disp_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(disp_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -360,7 +363,7 @@ def draw(self, context): prop_names, context, nt) else: draw_nodes_properties_ui( - layout, context, nt, input_name=self.shader_type) + layout, context, nt, input_name=input_name) class DATA_PT_renderman_light(ShaderPanel, Panel): bl_context = "data" @@ -474,7 +477,7 @@ def draw(self, context): if light.node_tree: nt = light.node_tree draw_nodes_properties_ui( - self.layout, context, nt, input_name='Light') + self.layout, context, nt, input_name='light_in') class DATA_PT_renderman_node_shader_lightfilter(ShaderNodePanel, Panel): bl_label = "Light Filter Parameters" @@ -494,7 +497,7 @@ def draw(self, context): if light.node_tree: nt = light.node_tree draw_nodes_properties_ui( - self.layout, context, nt, input_name='LightFilter') + self.layout, context, nt, input_name='lightfilter_in') class RENDERMAN_UL_LightFilters(CollectionPanel): def draw_item(self, layout, context, item): @@ -518,7 +521,7 @@ def draw_item(self, layout, context, item): nt = lightfilter.data.node_tree draw_nodes_properties_ui( - self.layout, context, nt, input_name='LightFilter') + self.layout, context, nt, input_name='lightfilter_in') else: layout.label(text='No light filter linked') @@ -557,7 +560,7 @@ def draw_item(self, layout, context, item): if portal.data.node_tree: nt = portal.data.node_tree draw_nodes_properties_ui( - self.layout, context, nt, input_name='Light') + self.layout, context, nt, input_name='lightfilter_in') else: layout.label(text='No portal light linked') diff --git a/rman_ui/rman_ui_object_panels.py b/rman_ui/rman_ui_object_panels.py index d8a0c117..fe22bae0 100644 --- a/rman_ui/rman_ui_object_panels.py +++ b/rman_ui/rman_ui_object_panels.py @@ -101,7 +101,6 @@ def draw_item(self, layout, context, item): def draw_props(self, layout, context): ob = context.object rm = ob.renderman - anim = rm.archive_anim_settings active = context.active_object rman_type = object_utils._detect_primitive_(active) @@ -151,8 +150,6 @@ def poll(cls, context): ob = context.object if ob.type != 'EMPTY': return False - if ob.is_instancer: - return False return (context.object and rd.engine in {'PRMAN_RENDER'} ) def draw(self, context): @@ -177,8 +174,6 @@ def poll(cls, context): ob = context.object if ob.type != 'EMPTY': return False - if ob.is_instancer: - return False mat = context.object.renderman.rman_material_override if not mat: return False @@ -238,14 +233,15 @@ def draw(self, context): col = split.column() layout.separator() - if not rman_output_node.inputs['Bxdf'].is_linked: + input_name = 'bxdf_in' + if not rman_output_node.inputs[input_name].is_linked: panel_node_draw(layout, context, mat, 'RendermanOutputNode', 'Bxdf') elif not filter_parameters or filter_method == 'NONE': panel_node_draw(layout, context, mat, 'RendermanOutputNode', 'Bxdf') elif filter_method == 'STICKY': - bxdf_node = rman_output_node.inputs['Bxdf'].links[0].from_node + bxdf_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(bxdf_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -254,7 +250,7 @@ def draw(self, context): expr = rman_output_node.bxdf_match_expression if expr == '': return - bxdf_node = rman_output_node.inputs['Bxdf'].links[0].from_node + bxdf_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(bxdf_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -344,14 +340,15 @@ def draw(self, context): col = split.column() shader_type = 'Displacement' - if not rman_output_node.inputs['Displacement'].is_linked: + input_name = 'displace_in' + if not rman_output_node.inputs[input_name].is_linked: draw_nodes_properties_ui( - layout, context, nt, input_name=shader_type) + layout, context, nt, input_name=input_name) elif not filter_parameters or filter_method == 'NONE': draw_nodes_properties_ui( - layout, context, nt, input_name=shader_type) + layout, context, nt, input_name=input_name) elif filter_method == 'STICKY': - disp_node = rman_output_node.inputs['Displacement'].links[0].from_node + disp_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(disp_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -360,7 +357,7 @@ def draw(self, context): expr = rman_output_node.disp_match_expression if expr == '': return - disp_node = rman_output_node.inputs['Displacement'].links[0].from_node + disp_node = rman_output_node.inputs[input_name].links[0].from_node nodes = gather_nodes(disp_node) for node in nodes: prop_names = getattr(node, 'prop_names', list()) @@ -368,7 +365,7 @@ def draw(self, context): prop_names, context, nt) else: draw_nodes_properties_ui( - layout, context, nt, input_name=shader_type) + layout, context, nt, input_name=input_name) class OBJECT_PT_renderman_object_geometry_quadric(Panel, CollectionPanel): bl_space_type = 'PROPERTIES' @@ -519,14 +516,7 @@ def draw(self, context): col = layout.column() col.enabled = not rman_interactive_running col = layout.column(align = True) - _draw_ui_from_rman_config('rman_properties_object', 'OBJECT_PT_renderman_object_geometry_rib_archive', context, layout, rm) - col.prop(anim, "animated_sequence") - if anim.animated_sequence: - col = layout.column(align = True) - col.prop(anim, "blender_start") - col.prop(anim, "sequence_in") - col.prop(anim, "sequence_out") - + _draw_ui_from_rman_config('rman_properties_object', 'OBJECT_PT_renderman_object_geometry_rib_archive', context, layout, rm) class OBJECT_PT_renderman_object_geometry_points(Panel, CollectionPanel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -579,7 +569,8 @@ def poll(cls, context): rm = context.object.renderman if context.object.type in ['LIGHT']: return False - if rm.primitive != 'RI_VOLUME': + rman_type = object_utils._detect_primitive_(context.object) + if rman_type not in ['OPENVDB', 'RI_VOLUME']: return False return (context.object and rd.engine in {'PRMAN_RENDER'}) @@ -708,7 +699,6 @@ def draw(self, context): layout = self.layout ob = context.object rm = ob.renderman - anim = rm.archive_anim_settings active = context.active_object rman_type = object_utils._detect_primitive_(active) diff --git a/rman_ui/rman_ui_render_panels.py b/rman_ui/rman_ui_render_panels.py index df559e34..a691a12f 100644 --- a/rman_ui/rman_ui_render_panels.py +++ b/rman_ui/rman_ui_render_panels.py @@ -58,7 +58,7 @@ def draw(self, context): _draw_ui_from_rman_config('rman_properties_scene', 'RENDER_PT_renderman_render', context, layout, rm) class RENDER_PT_renderman_spooling(PRManButtonsPanel, Panel): - bl_label = "External Rendering" + bl_label = "Batch Rendering" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): @@ -75,11 +75,11 @@ def draw(self, context): col = layout.column() row = col.row(align=True) rman_batch = rfb_icons.get_icon("rman_batch") - row.operator("renderman.external_render", - text="External Render", icon_value=rman_batch.icon_id) + row.operator("renderman.batch_render", + text="Batch Render", icon_value=rman_batch.icon_id) rman_bake = rfb_icons.get_icon("rman_bake") - row.operator("renderman.external_bake", - text="External Bake Render", icon_value=rman_bake.icon_id) + row.operator("renderman.batch_bake_render", + text="Batch Bake Render", icon_value=rman_bake.icon_id) # do animation col.prop(rm, 'external_animation') @@ -132,7 +132,7 @@ def draw(self, context): rm = world.renderman nt = world.node_tree - draw_nodes_properties_ui(layout, context, nt, input_name='Integrator', output_node_type='integrators_output') + draw_nodes_properties_ui(layout, context, nt, input_name='integrator_in', output_node_type='integrators_output') class RENDER_PT_renderman_world_display_filters(PRManButtonsPanel, Panel): bl_label = "Display Filters" @@ -175,7 +175,7 @@ def draw(self, context): op = col.operator("node.rman_remove_displayfilter_node_socket", text="", icon="REMOVE") op.index = i col = row.column() - col.label(text=socket.name) + col.label(text=socket.identifier) if socket.is_linked: col = row.column() @@ -248,7 +248,7 @@ def draw(self, context): op = col.operator("node.rman_remove_samplefilter_node_socket", text="", icon="REMOVE") op.index = i col = row.column() - col.label(text=socket.name) + col.label(text=socket.identifier) if socket.is_linked: col = row.column() @@ -364,11 +364,11 @@ def draw(self, context): RENDER_PT_renderman_render, RENDER_PT_renderman_spooling, RENDER_PT_renderman_spooling_export_options, + RENDER_PT_renderman_sampling, RENDER_PT_renderman_baking, RENDER_PT_renderman_world_integrators, RENDER_PT_renderman_world_display_filters, RENDER_PT_renderman_world_sample_filters, - RENDER_PT_renderman_sampling, RENDER_PT_renderman_motion_blur, RENDER_PT_renderman_advanced_settings, RENDER_PT_renderman_custom_options diff --git a/rman_ui/rman_ui_texteditor.py b/rman_ui/rman_ui_texteditor.py index 319e0d5d..0243f80e 100644 --- a/rman_ui/rman_ui_texteditor.py +++ b/rman_ui/rman_ui_texteditor.py @@ -1,4 +1,5 @@ from ..rman_constants import RFB_ADDON_PATH +from ..rfb_logger import rfb_log import bpy import os diff --git a/rman_ui/rman_ui_txmanager.py b/rman_ui/rman_ui_txmanager.py index ecafb7b8..19382bb3 100644 --- a/rman_ui/rman_ui_txmanager.py +++ b/rman_ui/rman_ui_txmanager.py @@ -7,17 +7,73 @@ from ..rfb_utils import shadergraph_utils from ..rfb_utils import scene_utils from ..rfb_utils import object_utils -from ..rfb_utils.prefs_utils import get_pref +from ..rfb_utils.prefs_utils import get_pref, using_qt from ..rfb_logger import rfb_log from ..rman_config import __RFB_CONFIG_DICT__ as rfb_config +from ..rman_constants import RFB_HELP_URL from .. import rman_render from rman_utils.txmanager import txparams from rman_utils import txmanager as txmngr from .. import rfb_icons +import sys +import hashlib import os import uuid - +__TXMANAGER_WINDOW__ = None + +if not bpy.app.background: + from ..rman_ui import rfb_qt + + class TxManagerQtAppTimed(rfb_qt.RfbBaseQtAppTimed): + bl_idname = "wm.txm_qt_app_timed" + bl_label = "Texture Manager" + + def __init__(self): + super(TxManagerQtAppTimed, self).__init__() + + def execute(self, context): + self._window = create_widget() + return super(TxManagerQtAppTimed, self).execute(context) + + def parse_scene(): + from ..rfb_utils import texture_utils + bl_scene = bpy.context.scene + mgr = texture_utils.get_txmanager().txmanager + mgr.reset() + texture_utils.parse_for_textures(bl_scene) + + def _append_to_tx_list(file_path_list): + """Called by the txmanager when extra files are added to the scene list. + """ + from ..rfb_utils import texture_utils + bl_scene = bpy.context.scene + txmgr = texture_utils.get_txmanager().txmanager + texture_utils.parse_for_textures(bl_scene) + for fpath in file_path_list: + # Pass None as the nodeID and a hash will be generated. + texid = hashlib.sha1(fpath.encode('utf-8')).hexdigest() + txmgr.add_texture(texid, fpath) + txmgr.update_ui_list() + # make sure to restart the queue. + txmgr.txmake_all(start_queue=True, blocking=False) + + def help_func(url): + bpy.ops.wm.url_open(url = RFB_HELP_URL) + + def create_widget(): + global __TXMANAGER_WINDOW__ + if not __TXMANAGER_WINDOW__: + import rman_utils.txmanager.ui as rui + from ..rfb_utils import texture_utils + mgr = texture_utils.get_txmanager().txmanager + __TXMANAGER_WINDOW__ = rui.TxManagerUI(None, txmanager=mgr, + parse_scene_func=parse_scene, + append_tx_func=_append_to_tx_list, + help_func=help_func) + mgr.ui = __TXMANAGER_WINDOW__ + return __TXMANAGER_WINDOW__ + class TxFileItem(PropertyGroup): """UIList item representing a TxFile""" @@ -669,13 +725,8 @@ def draw_txmanager_layout(cls, context, layout): def draw(self, context): layout = self.layout if get_pref('rman_ui_framework') == 'QT': - try: - from . import rman_ui_txmanager_qt - if rman_ui_txmanager_qt.__QT_LOADED__: - rman_icon = rfb_icons.get_icon('rman_txmanager') - layout.operator("rman_txmgr_list.open_txmanager", icon_value=rman_icon.icon_id) - except: - PRMAN_PT_Renderman_txmanager_list.draw_txmanager_layout(context, layout) + rman_icon = rfb_icons.get_icon('rman_txmanager') + layout.operator("rman_txmgr_list.open_txmanager", icon_value=rman_icon.icon_id) else: PRMAN_PT_Renderman_txmanager_list.draw_txmanager_layout(context, layout) @@ -701,6 +752,24 @@ def __init__(self): self.event = None def invoke(self, context, event): + if using_qt(): + global __TXMANAGER_WINDOW__ + if __TXMANAGER_WINDOW__ and __TXMANAGER_WINDOW__.isVisible(): + return {'FINISHED'} + + if sys.platform == "darwin": + rfb_qt.run_with_timer(__TXMANAGER_WINDOW__, create_widget) + else: + bpy.ops.wm.txm_qt_app_timed() + mgr = texture_utils.get_txmanager().txmanager + mgr.update_ui_list() + if self.nodeID: + txfile = mgr.get_txfile_from_id(self.nodeID) + mgr.ui.select_txfile(txfile) + + return {'FINISHED'} + + if self.properties.nodeID != '': for i, item in enumerate(context.scene.rman_txmgr_list): if item.nodeID == self.properties.nodeID: @@ -768,21 +837,16 @@ def index_updated(self, context): PRMAN_OT_Renderman_txmanager_add_texture, PRMAN_OT_Renderman_txmanager_refresh, PRMAN_PT_Renderman_txmanager_list, - PRMAN_OT_Renderman_txmanager_remove_texture -] + PRMAN_OT_Renderman_txmanager_remove_texture, + PRMAN_OT_Renderman_open_txmanager, +] + +if not bpy.app.background: + classes.append(TxManagerQtAppTimed) def register(): - from ..rfb_utils import register_utils - - if get_pref('rman_ui_framework') == 'QT': - try: - from . import rman_ui_txmanager_qt - rman_ui_txmanager_qt.register() - except: - register_utils.rman_register_class(PRMAN_OT_Renderman_open_txmanager) - else: - register_utils.rman_register_class(PRMAN_OT_Renderman_open_txmanager) + from ..rfb_utils import register_utils register_utils.rman_register_classes(classes) @@ -796,10 +860,5 @@ def unregister(): del bpy.types.Scene.rman_txmgr_list_index from ..rfb_utils import register_utils - - register_utils.rman_unregister_classes(classes) - try: - from . import rman_ui_txmanager_qt - rman_ui_txmanager_qt.unregister() - except: - pass \ No newline at end of file + + register_utils.rman_unregister_classes(classes) \ No newline at end of file diff --git a/rman_ui/rman_ui_txmanager_qt.py b/rman_ui/rman_ui_txmanager_qt.py deleted file mode 100644 index 15297a25..00000000 --- a/rman_ui/rman_ui_txmanager_qt.py +++ /dev/null @@ -1,97 +0,0 @@ -__QT_LOADED__ = False - -try: - from ..rman_ui import rfb_qt -except: - raise - -import sys -import bpy -import hashlib - -from ..rfb_logger import rfb_log - -__QT_LOADED__ = True -__TXMANAGER_WINDOW__ = None - -class TxManagerQtAppTimed(rfb_qt.RfbBaseQtAppTimed): - bl_idname = "wm.txm_qt_app_timed" - bl_label = "Texture Manager" - - def __init__(self): - super(TxManagerQtAppTimed, self).__init__() - - def execute(self, context): - self._window = create_widget() - return super(TxManagerQtAppTimed, self).execute(context) - -def parse_scene(): - from ..rfb_utils import texture_utils - bl_scene = bpy.context.scene - mgr = texture_utils.get_txmanager().txmanager - mgr.reset() - texture_utils.parse_for_textures(bl_scene) - -def _append_to_tx_list(file_path_list): - """Called by the txmanager when extra files are added to the scene list. - """ - from ..rfb_utils import texture_utils - bl_scene = bpy.context.scene - txmgr = texture_utils.get_txmanager().txmanager - texture_utils.parse_for_textures(bl_scene) - for fpath in file_path_list: - # Pass None as the nodeID and a hash will be generated. - texid = hashlib.sha1(fpath.encode('utf-8')).hexdigest() - txmgr.add_texture(texid, fpath) - txmgr.update_ui_list() - # make sure to restart the queue. - txmgr.txmake_all(start_queue=True, blocking=False) - -def create_widget(): - global __TXMANAGER_WINDOW__ - if not __TXMANAGER_WINDOW__: - import rman_utils.txmanager.ui as rui - from ..rfb_utils import texture_utils - mgr = texture_utils.get_txmanager().txmanager - __TXMANAGER_WINDOW__ = rui.TxManagerUI(None, txmanager=mgr, - parse_scene_func=parse_scene, - append_tx_func=_append_to_tx_list, - help_func=None) - mgr.ui = __TXMANAGER_WINDOW__ - return __TXMANAGER_WINDOW__ - -class PRMAN_OT_TxManager_Qt(bpy.types.Operator): - bl_idname = "rman_txmgr_list.open_txmanager" - bl_label = "Texture Manager" - - nodeID: bpy.props.StringProperty(default='') - - def execute(self, context): - from ..rfb_utils import texture_utils - global __TXMANAGER_WINDOW__ - if sys.platform == "darwin": - rfb_qt.run_with_timer(__TXMANAGER_WINDOW__, create_widget) - else: - bpy.ops.wm.txm_qt_app_timed() - mgr = texture_utils.get_txmanager().txmanager - mgr.update_ui_list() - if self.nodeID: - txfile = mgr.get_txfile_from_id(self.nodeID) - mgr.ui.select_txfile(txfile) - - return {'RUNNING_MODAL'} - -classes = [ - PRMAN_OT_TxManager_Qt, - TxManagerQtAppTimed -] - -def register(): - from ..rfb_utils import register_utils - - register_utils.rman_register_classes(classes) - -def unregister(): - from ..rfb_utils import register_utils - - register_utils.rman_unregister_classes(classes) \ No newline at end of file diff --git a/rman_ui/rman_ui_view3d_menus.py b/rman_ui/rman_ui_view3d_menus.py index 3ab0dee3..b0f18ac9 100644 --- a/rman_ui/rman_ui_view3d_menus.py +++ b/rman_ui/rman_ui_view3d_menus.py @@ -5,6 +5,7 @@ from ..rfb_utils import shadergraph_utils from ..rfb_utils import object_utils from ..rfb_utils.envconfig_utils import envconfig +from ..rfb_utils.prefs_utils import using_qt, show_wip_qt from ..rfb_logger import rfb_log from ..rman_config import __RFB_CONFIG_DICT__ as rfb_config from bpy.types import Menu @@ -268,6 +269,8 @@ def draw(self, context): return if light_props.renderman_light_role not in {'RMAN_LIGHTFILTER', 'RMAN_LIGHT'}: return + if using_qt() and show_wip_qt(): + return selected_objects = context.selected_objects if selected_objects: layout.context_pointer_set('light_ob', active_light) @@ -338,6 +341,8 @@ def draw(self, context): rman_vol_agg = rfb_icons.get_icon("rman_vol_aggregates") layout.operator("scene.rman_open_vol_aggregates_editor", text="Volume Aggregates Editor", icon_value=rman_vol_agg.icon_id) layout.separator() + if using_qt() and show_wip_qt(): + return op = layout.operator("renderman.add_remove_volume_aggregates", text="Create New Group") op.context="scene.renderman" op.collection="vol_aggregates" @@ -354,6 +359,8 @@ def draw(self, context): layout.separator() layout.label(text='Add Selected To: ') for i, v in enumerate(vol_aggregates): + if i == 0: + continue op = layout.operator('renderman.add_to_vol_aggregate', text=v.name) op.vol_aggregates_index = i op.do_scene_selected = True @@ -406,8 +413,12 @@ def draw(self, context): if not rm.is_rman_interactive_running: rman_rerender_controls = rfb_icons.get_icon("rman_ipr_on") - layout.operator('renderman.start_ipr', text="IPR", + op = layout.operator('renderman.start_ipr', text="IPR", + icon_value=rman_rerender_controls.icon_id) + op.render_to_it = False + op = layout.operator('renderman.start_ipr', text="IPR to it", icon_value=rman_rerender_controls.icon_id) + op.render_to_it = True rman_render_icon = rfb_icons.get_icon("rman_render") layout.operator("render.render", text="Render", icon_value=rman_render_icon.icon_id) @@ -468,6 +479,8 @@ def draw(self, context): scene = context.scene op = layout.operator("scene.rman_open_groups_editor", text="Trace Sets Editor") + if using_qt() and show_wip_qt(): + return selected_objects = [] if context.selected_objects: for obj in context.selected_objects: @@ -493,7 +506,7 @@ def draw(self, context): for i, obj_grp in enumerate(obj_grps.keys()): op = layout.operator('renderman.add_to_group', text=obj_grp) op.do_scene_selected = True - op.open_editor = True + op.open_editor = not using_qt() op.group_index = i class VIEW3D_MT_RM_Add_Selected_To_LightMixer_Menu(bpy.types.Menu): @@ -510,6 +523,8 @@ def draw(self, context): scene = context.scene layout.operator('scene.rman_open_light_mixer_editor', text='Light Mixer Editor') layout.separator() + if using_qt() and show_wip_qt(): + return selected_light_objects = [] if context.selected_objects: for obj in context.selected_objects: diff --git a/rman_ui/rman_ui_view3d_panels.py b/rman_ui/rman_ui_view3d_panels.py index b1f59c3f..75df6d35 100644 --- a/rman_ui/rman_ui_view3d_panels.py +++ b/rman_ui/rman_ui_view3d_panels.py @@ -5,6 +5,7 @@ from ..rfb_logger import rfb_log from .rman_ui_base import _RManPanelHeader from ..rman_render import RmanRender +from ..rman_constants import RFB_HELP_URL import bpy class PRMAN_PT_Renderman_UI_Panel(bpy.types.Panel, _RManPanelHeader): @@ -70,28 +71,14 @@ def draw(self, context): row = layout.row(align=True) rman_rerender_controls = rfb_icons.get_icon("rman_ipr_on") - row.operator('renderman.start_ipr', text="IPR", + op = row.operator('renderman.start_ipr', text="Start IPR to 'it'", icon_value=rman_rerender_controls.icon_id) - - row.prop(context.scene, "rm_ipr", text="", - icon=draw_utils.get_open_close_icon(context.scene.rm_ipr)) - - if context.scene.rm_ipr: - scene = context.scene - rd = scene.render - - box = layout.box() - box.use_property_split = True - box.use_property_decorate = False - row = box.row(align=True) - - # Display Driver - row.prop(rm, "render_ipr_into") + op.render_to_it = True row = layout.row(align=True) rman_batch = rfb_icons.get_icon("rman_batch") - row.operator("renderman.external_render", + row.operator("renderman.batch_render", text="External Render", icon_value=rman_batch.icon_id) row.prop(context.scene, "rm_render_external", text="", @@ -251,16 +238,18 @@ def draw(self, context): layout.label(text='Utilities:') box = layout.box() rman_addon_prefs = rfb_icons.get_icon('rman_loadplugin') - box.operator("renderman.open_addon_preferences", icon_value=rman_addon_prefs.icon_id) + op = box.operator("preferences.addon_show", text="Addon Preferences", icon_value=rman_addon_prefs.icon_id) + op.module = "RenderManForBlender" rman_pack_scene = rfb_icons.get_icon('rman_package_scene') box.operator("renderman.scene_package", icon_value=rman_pack_scene.icon_id) + box.operator("renderman.upgrade_scene", icon='FILE_REFRESH') layout.separator() # RenderMan Doc layout.label(text="Help:") rman_help = rfb_icons.get_icon("rman_help") layout.operator("wm.url_open", text="RenderMan Docs", - icon_value=rman_help.icon_id).url = "https://rmanwiki.pixar.com/display/RFB24" + icon_value=rman_help.icon_id).url = RFB_HELP_URL rman_info = rfb_icons.get_icon("rman_blender") layout.operator("renderman.about_renderman", icon_value=rman_info.icon_id) @@ -277,7 +266,7 @@ def draw(self, context): scene = context.scene rm = scene.renderman rr = RmanRender.get_rman_render() - if hasattr(bpy.types, bpy.ops.renderman.rman_open_stats.idname()): + if prefs_utils.using_qt(): layout.separator() layout.operator("renderman.rman_open_stats") if rr.stats_mgr.is_connected(): diff --git a/rman_ui/rman_ui_viewport.py b/rman_ui/rman_ui_viewport.py index 8adbf6af..5a4cfd5e 100644 --- a/rman_ui/rman_ui_viewport.py +++ b/rman_ui/rman_ui_viewport.py @@ -6,6 +6,7 @@ from ..rfb_utils.prefs_utils import get_pref, get_addon_prefs from ..rfb_utils import display_utils from ..rfb_utils import camera_utils +from ..rfb_logger import rfb_log from bpy.types import Menu import bpy @@ -94,7 +95,7 @@ def draw(self, context): layout = self.layout rman_render = RmanRender.get_rman_render() rman_render.rman_scene._find_renderman_layer() - dspys_dict = display_utils.get_dspy_dict(rman_render.rman_scene) + dspys_dict = display_utils.get_dspy_dict(rman_render.rman_scene, include_holdouts=False) for chan_name, chan_params in dspys_dict['channels'].items(): layout.operator_context = 'EXEC_DEFAULT' op = layout.operator('renderman_viewport.channel_selector', text=chan_name) @@ -137,10 +138,8 @@ class PRMAN_OT_Viewport_Refinement(bpy.types.Operator): ) def execute(self, context): - rman_render = RmanRender.get_rman_render() rm = context.scene.renderman rm.hider_decidither = int(self.viewport_hider_decidither) - rman_render.rman_scene_sync.update_global_options(context) return {"FINISHED"} @@ -191,7 +190,7 @@ class PRMAN_OT_Viewport_Snapshot(bpy.types.Operator): def execute(self, context): rman_render = RmanRender.get_rman_render() scene = context.scene - rman_render.save_viewport_snapshot(frame=scene.frame_current) + rman_render.save_viewport_snapshot() return {"FINISHED"} @@ -420,8 +419,6 @@ class PRMAN_OT_Viewport_Enhance(bpy.types.Operator): bl_description = "Enhance" bl_options = {"INTERNAL"} - zoom_factor: FloatProperty(name="Zoom", default=5.0, min=1.0, max=5.0) - def __init__(self): self.x = -1 self.y = -1 @@ -439,8 +436,9 @@ def poll(cls, context): @classmethod def description(cls, context, properties): help = "NOTE: This only works with perspective cameras or the PxrCamera projection plugin.\n\n" - help += "Embiggens the region around a pixel (X,Y) by zoom" - help += "\nfactor for trouble-shooting. The magnified pixel will remain" + help += "Embiggens the region around a pixel (X,Y) by a zoom" + help += "\nfactor for trouble-shooting. The zoom factor can be changed" + help += "in the preferences. The magnified pixel will remain" help += "\nanchored in place relative to the image. Camera effects such as" help += "\nvignetting will be scaled accordingly. Intentionally does not" help += "\naffect level-of-detail, dicing, displacement, or MIP map levels." @@ -448,10 +446,13 @@ def description(cls, context, properties): help += "\n\nEnter to simply exit out of the operator, and keep the current zoom. Esc to exit and reset the zoom." return help + def get_zoom_factor(self): + zoom_factor = float(get_pref('rman_enhance_zoom_factor')) + return zoom_factor def execute(self, context): rman_render = RmanRender.get_rman_render() - rman_render.rman_scene_sync.update_enhance(context, self.x, self.y, self.zoom_factor) + rman_render.rman_scene_sync.update_enhance(context, self.x, self.y, self.get_zoom_factor()) return {'RUNNING_MODAL'} @@ -461,7 +462,7 @@ def reset(self, context): def call_upate(self, context, x, y): rman_render = RmanRender.get_rman_render() - rman_render.rman_scene_sync.update_enhance(context, x, y, self.zoom_factor) + rman_render.rman_scene_sync.update_enhance(context, x, y, self.get_zoom_factor()) def modal(self, context, event): x = event.mouse_region_x @@ -791,6 +792,23 @@ def invoke(self, context, event): self.crop_handler.crop_windowing = True return {'RUNNING_MODAL'} +class PRMAN_MT_Viewport_Render_Menu(Menu): + bl_label = "Render Viewport Menu" + bl_idname = "PRMAN_MT_Viewport_Render_Menu" + + @classmethod + def poll(cls, context): + return context.engine == "PRMAN_RENDER" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + op = layout.operator('renderman.start_ipr', text='IPR to Viewport', icon='BLENDER') + op.render_to_it = False + rman_icon = rfb_icons.get_icon('rman_it') + op = layout.operator('renderman.start_ipr', text='IPR to it', icon_value=rman_icon.icon_id) + op.render_to_it = True + def draw_rman_viewport_props(self, context): layout = self.layout scene = context.scene @@ -800,7 +818,9 @@ def draw_rman_viewport_props(self, context): if context.engine == "PRMAN_RENDER": view = context.space_data rman_render = RmanRender.get_rman_render() - if view.shading.type == 'RENDERED': + if view.shading.type == 'RENDERED' or rman_render.is_ipr_to_it(): + if not rman_render.rman_running: + return rman_rerender_controls = rfb_icons.get_icon("rman_ipr_cancel") row.operator('renderman.stop_ipr', text="", icon_value=rman_rerender_controls.icon_id) @@ -833,6 +853,7 @@ def draw_rman_viewport_props(self, context): # texture cache clear rman_icon = rfb_icons.get_icon('rman_lightning_grey') row.operator('rman_txmgr_list.clear_all_cache', text='', icon_value=rman_icon.icon_id) + elif rman_render.rman_running: rman_rerender_controls = rfb_icons.get_icon("rman_ipr_cancel") row.operator('renderman.stop_render', text="", @@ -846,8 +867,7 @@ def draw_rman_viewport_props(self, context): #rman_render.stop_render() rman_render.del_bl_engine() rman_rerender_controls = rfb_icons.get_icon("rman_ipr_on") - row.operator('renderman.start_ipr', text="", - icon_value=rman_rerender_controls.icon_id) + row.menu('PRMAN_MT_Viewport_Render_Menu', text='', icon_value=rman_rerender_controls.icon_id) row.popover(panel="PRMAN_PT_Viewport_Options", text="") @@ -881,7 +901,12 @@ def draw(self, context): col.prop(prefs, 'rman_viewport_draw_progress') if prefs.rman_viewport_draw_progress: col.prop(prefs, 'rman_viewport_progress_color') - col.prop(prefs, 'draw_ipr_text') + col.prop(prefs, 'rman_enhance_zoom_factor') + if rm.current_platform != ("macOS"): + col = layout.column(align=True) + col.prop(rm, 'blender_ipr_optix_denoiser') + if rman_render.rman_interactive_running: + col.enabled = False if rm.current_platform != ("macOS") and rm.has_xpu_license: col = layout.column(align=True) @@ -909,7 +934,8 @@ def draw(self, context): PRMAN_OT_Viewport_CropWindow_Reset, PRMAN_OT_Viewport_Cropwindow, PRMAN_OT_Viewport_Enhance, - PRMAN_PT_Viewport_Options + PRMAN_PT_Viewport_Options, + PRMAN_MT_Viewport_Render_Menu ] def register(): diff --git a/rman_ui/rman_ui_world_panels.py b/rman_ui/rman_ui_world_panels.py index bd2268b6..beeb1607 100644 --- a/rman_ui/rman_ui_world_panels.py +++ b/rman_ui/rman_ui_world_panels.py @@ -57,7 +57,7 @@ def draw(self, context): rm = world.renderman nt = world.node_tree - draw_nodes_properties_ui(layout, context, nt, input_name='Integrator', output_node_type='integrators_output') + draw_nodes_properties_ui(layout, context, nt, input_name='integrator_in', output_node_type='integrators_output') class DATA_PT_renderman_world_display_filters(ShaderPanel, Panel): bl_label = "Display Filters"