diff --git a/__init__.py b/__init__.py index 54f5804d..f95aee8e 100644 --- a/__init__.py +++ b/__init__.py @@ -36,7 +36,7 @@ bl_info = { "name": "RenderMan For Blender", "author": "Pixar", - "version": (24, 3, 0), + "version": (24, 4, 0), "blender": (2, 83, 0), "location": "Info Header, render engine menu", "description": "RenderMan 24 integration", diff --git a/changelog.txt b/changelog.txt index bced096b..9e636ddc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,53 @@ +v24.4 April 22, 2022 + +Changes: +* Live stats are now enabled by default in the preferences. +* Setting the environment variable, RFB_BATCH_NO_PROGRESS, will disable progress printing during batch rendering via Blender. + +Bug Fixes: +* Fix issue with depth of field not matching between viewport renders and preview renders +* Fix issue where rotating dome lights would cause portal lights to rotate on the wrong axis. +* An issue where textures in the texture manager would fail when materials/nodes were renamed has been addressed. +* Fix issue where light filters were still being drawn in the viewport when they were deleted. +* Holdouts should now render correctly when doing viewport renders +* Lights are correctly hidden when the viewports overlays has been disabled +* Fix an issue with progress not displaying correctly when rendering to "it". + +v24.3 January 6, 2022 + +New Features: +* A cone shape will now be drawn in the viewport to represent the cone angle for lights that +support the Cone Angle parameter, emulating behavior that is present in the other RenderMan DCC +integrations. Light support this include PxrRectLight, PxrDiskLight, PxrSphereLight, and PxrCylinderLight. +Two new properties, Cone Angle Depth and Cone Angle Opacity, controls the depth and opacity of the cone +draw in the viewport. +* Add a Reset entry in the viewport integrators menu. Selecting Reset will reset +back to the scene integrator +* Added a pref to turn off the use of the token to reference +relative paths + +Changes: +* Rendering in the viewport with RIS should be slightly faster. +* The UI for arrays has changed. The addon will try to update any older scenes to use the +new arrays, but it is not guaranteed to work in all conditions, and may require +rebuilding/reconnecting of the shader network. +* The addon will now warn if the minor version of RenderMan selected is not compatible. +* When rendering hair, the mesh vertex colors will be added to the hair +as primitive variable "Cs". +* You can now choose a separate UV map and vertex colors for hair other than the current +active one. + +Bug Fixes: +* Fixed an issue where importing SVGs as curves did not render correctly. +* Fixed a bug where bump and presence were not connectable on PxrSurface +* Fixed the enable parameter labels on PxrLayer +* Fixed a bug that caused light linking of LightFilters to not work +* Fixed an issue where the token was not working properly +* Fixed an issue where inputAOV on PxrDisneyBsdf was not connectable +* Fixed an issue where utilityInteger on LamaSurface was not connectable +* Fixed an issue where the bump2roughness settings in the texture manager +would reset after changes were applied. + v24.2 November 9, 2021 New Features: * Add a new Volume Aggregates editor. For more information on volume aggregates, see the documentation diff --git a/preferences.py b/preferences.py index 965348f8..94819c3f 100644 --- a/preferences.py +++ b/preferences.py @@ -504,11 +504,14 @@ def update_stats_config(self, context): update=update_stats_config ) rman_roz_grpcServer: BoolProperty(name="Send Stats to 'it' HUD", default=True, - description="Turn this off if you don't want stats to be sent to the 'it' HUD.", + description="(DEPRECATED) Turn this off if you don't want stats to be sent to the 'it' HUD.", update=update_stats_config) rman_roz_webSocketServer: BoolProperty(name="Enable Live Stats", default=False, - description="Turning this off will disable the live statistics system in RfB.", + description="(DEPRECATED) Turning this off will disable the live statistics system in RfB.", update=update_stats_config) + rman_roz_liveStatsEnabled: BoolProperty(name="Enable Live Stats", default=True, + description="Turning this off will disable the live statistics system in RfB.", + update=update_stats_config) rman_roz_webSocketServer_Port: IntProperty(name="Port", default=0, min=0, description="Port number of the live stats server to use. Setting to 0 will randomly select an open port.", @@ -664,10 +667,9 @@ def draw(self, context): row = col.row() col = row.column() col.prop(self, 'rman_roz_logLevel') - col.prop(self, 'rman_roz_grpcServer') - col.prop(self, 'rman_roz_webSocketServer') + col.prop(self, 'rman_roz_liveStatsEnabled') - if self.rman_roz_webSocketServer: + if self.rman_roz_liveStatsEnabled: try: from .rman_stats import RfBStatsManager stats_mgr = RfBStatsManager.get_stats_manager() diff --git a/rfb_utils/property_utils.py b/rfb_utils/property_utils.py index d9db2463..810f9e8b 100644 --- a/rfb_utils/property_utils.py +++ b/rfb_utils/property_utils.py @@ -762,8 +762,7 @@ def set_node_rixparams(node, rman_sg_node, params, ob=None, mat_name=None, group val = string_utils.expand_string(prop) options = meta['options'] if bl_prop_info.is_texture: - tx_node_id = texture_utils.generate_node_id(node, param_name, ob=ob) - tx_val = texture_utils.get_txmanager().get_output_tex_from_id(tx_node_id) + tx_val = texture_utils.get_txmanager().get_output_tex_from_path(node, param_name, val, ob=ob) val = tx_val if tx_val != '' else val elif param_widget == 'assetidoutput': display = 'openexr' @@ -772,8 +771,13 @@ def set_node_rixparams(node, rman_sg_node, params, ob=None, mat_name=None, group val = string_utils.expand_string(prop, display=display, asFilePath=True) else: val = string_utils.convert_val(prop, type_hint=param_type) + is_array = False + array_len = -1 + if bl_prop_info.arraySize: + is_array = True + array_len = int(bl_prop_info.arraySize) - set_rix_param(params, param_type, param_name, val, is_reference=False, node=node) + set_rix_param(params, param_type, param_name, val, is_reference=False, is_array=is_array, array_len=array_len, node=node) return params diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index 5ae9e72e..5fa43443 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -379,35 +379,58 @@ def gather_nodes(node): return nodes -def gather_all_nodes_for_material(mat, nodes_list): - for node in mat.node_tree.nodes: +def gather_all_nodes_for_material(ob, nodes_list): + for node in ob.node_tree.nodes: if node not in nodes_list: + if isinstance(ob, bpy.types.ShaderNodeGroup): + nodes_list.insert(0, node) + else: + nodes_list.append(node) + if node.bl_idname == 'ShaderNodeGroup': + gather_all_nodes_for_material(node, nodes_list) + +def gather_all_textured_nodes(ob, nodes_list): + nt = None + if isinstance(ob, bpy.types.Object): + if ob.type == 'LIGHT': + nt = ob.data.node_tree + elif isinstance(ob, bpy.types.Material): + nt = ob.node_tree + elif isinstance(ob, bpy.types.ShaderNodeGroup): + nt = ob.node_tree + if nt is None: + return + + for node in nt.nodes: + has_textured_params = getattr(node, 'rman_has_textured_params', False) + if node not in nodes_list and has_textured_params: nodes_list.append(node) if node.bl_idname == 'ShaderNodeGroup': - for n in node.node_tree.nodes: - nodes_list.insert(0, n) + gather_all_textured_nodes(node, nodes_list) def get_nodetree_name(node): - nt = node.id_data + nt = node.id_data.original for nm, ng in bpy.data.node_groups.items(): - if nt == ng: + if nt == ng.original: return nm for mat in bpy.data.materials: if mat.node_tree is None: continue - if mat.node_tree == nt: + if mat.node_tree.original == nt: return mat.name for world in bpy.data.worlds: - if world.node_tree == nt: + if world.node_tree.original == nt: return world.name for ob in bpy.data.objects: if ob.type == 'LIGHT': light = ob.data - if light.node_tree == nt: + if light.node_tree is None: + continue + if light.node_tree.original == nt: return ob.name elif ob.type == 'CAMERA': if find_projection_node(ob) == node: diff --git a/rfb_utils/texture_utils.py b/rfb_utils/texture_utils.py index c6da8747..52006e0f 100644 --- a/rfb_utils/texture_utils.py +++ b/rfb_utils/texture_utils.py @@ -15,14 +15,44 @@ from bpy.app.handlers import persistent import os -import glob -import subprocess import bpy import uuid import re __RFB_TXMANAGER__ = None +def get_nodeid(node): + """Return the contents of the 'txm_id' attribute of a node. + Returns None if the attribute doesn't exist.""" + try: + tokens = node.split('|') + if len(tokens) < 2: + return "" + node_tree = tokens[0] + node_name = tokens[1] + node, ob = scene_utils.find_node_by_name(node_name, node_tree) + if node is None: + return None + txm_id = getattr(node, 'txm_id') + return txm_id + except ValueError: + return None + + +def set_nodeid(node, node_id): + """Set a node's 'txm_id' attribute with node_id. If the attribute doesn't + exist yet, it will be created.""" + tokens = node.split('|') + if len(tokens) < 2: + return "" + node_tree = tokens[0] + node_name = tokens[1] + node, ob = scene_utils.find_node_by_name(node_name, node_tree) + if node: + try: + setattr(node, 'txm_id', node_id) + except AttributeError: + return "" class RfBTxManager(object): def __init__(self): @@ -32,7 +62,10 @@ def __init__(self): host_load_func=load_scene_state, host_save_func=save_scene_state, texture_extensions=self.get_ext_list(), - color_manager=color_manager()) + color_manager=color_manager(), + set_nodeid_func=set_nodeid, + get_nodeid_func=get_nodeid + ) self.rman_scene = None @property @@ -101,7 +134,29 @@ def get_output_tex(self, txfile): return output_tex + def get_output_tex_from_path(self, node, param_name, file_path, ob=None): + node_name = generate_node_name(node, param_name, ob=ob) + plug_uuid = self.txmanager.get_plug_id(node_name, param_name) + # lookup the txmanager for an already converted texture + txfile = self.txmanager.get_txfile_from_id(plug_uuid) + if txfile is None: + if file_path == '': + return file_path + category = getattr(node, 'renderman_node_type', 'pattern') + node_type = '' + node_type = node.bl_label + self.txmanager.add_texture(plug_uuid, file_path, nodetype=node_type, category=category) + txfile = self.txmanager.get_txfile_from_id(plug_uuid) + bpy.ops.rman_txmgr_list.add_texture('EXEC_DEFAULT', filepath=file_path, nodeID=plug_uuid) + txmake_all(blocking=False) + if txfile: + self.done_callback(plug_uuid, txfile) + if txfile: + return self.get_output_tex(txfile) + + return file_path + def get_output_tex_from_id(self, nodeID): ''' Get the real output texture path given a nodeID @@ -112,30 +167,6 @@ def get_output_tex_from_id(self, nodeID): return self.get_output_tex(txfile) - def get_output_vdb(self, ob): - ''' - Get the mipmapped version of the openvdb file - ''' - if ob.type != 'VOLUME': - return '' - db = ob.data - grids = db.grids - - 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 = string_utils.get_tokenized_openvdb_file(grids.frame_filepath, grids.frame) - - nodeID = generate_node_id(None, 'filepath', ob=ob) - if self.does_nodeid_exists(nodeID): - openvdb_file = self.get_output_tex_from_id(nodeID) - return openvdb_file - return openvdb_file - - def get_txfile_for_vdb(self, ob): - nodeID = generate_node_id(None, 'filepath', ob=ob) - return self.get_txfile_from_id(nodeID) - def get_txfile_from_id(self, nodeID): ''' Get the txfile from given a nodeID @@ -151,25 +182,44 @@ def txmake_all(self, blocking=True): self.txmanager.txmake_all(start_queue=True, blocking=blocking) def add_texture(self, node, ob, param_name, file_path, node_type='PxrTexture', category='pattern'): - nodeID = generate_node_id(node, param_name, ob=ob) + node_name = generate_node_name(node, param_name, ob=ob) + plug_uuid = self.txmanager.get_plug_id(node_name, param_name) if file_path == "": - txfile = self.txmanager.get_txfile_from_id(nodeID) + txfile = self.txmanager.get_txfile_from_id(plug_uuid) if txfile: - self.txmanager.remove_texture(nodeID) - bpy.ops.rman_txmgr_list.remove_texture('EXEC_DEFAULT', nodeID=nodeID) + self.txmanager.remove_texture(plug_uuid) + bpy.ops.rman_txmgr_list.remove_texture('EXEC_DEFAULT', nodeID=plug_uuid) else: - txfile = self.txmanager.add_texture(nodeID, file_path, nodetype=node_type, category=category) - bpy.ops.rman_txmgr_list.add_texture('EXEC_DEFAULT', filepath=file_path, nodeID=nodeID) - txmake_all(blocking=False) - if txfile: - self.done_callback(nodeID, txfile) - self.txmanager.update_ui_list() + # lookup the txmanager for an already converted texture + txfile = self.txmanager.get_txfile_from_id(plug_uuid) + if txfile is None or txfile.input_image != file_path: + self.txmanager.add_texture(plug_uuid, file_path, nodetype=node_type, category=category) + bpy.ops.rman_txmgr_list.add_texture('EXEC_DEFAULT', filepath=file_path, nodeID=plug_uuid) + txfile = self.txmanager.get_txfile_from_id(plug_uuid) + txmake_all(blocking=False) + if txfile: + self.done_callback(plug_uuid, txfile) def is_file_src_tex(self, node, prop_name): id = scene_utils.find_node_owner(node) nodeID = generate_node_id(node, prop_name, ob=id) txfile = self.get_txfile_from_id(nodeID) + if txfile is None: + category = getattr(node, 'renderman_node_type', 'pattern') + node_type = '' + node_type = node.bl_label + file_path = getattr(node, prop_name) + self.txmanager.add_texture(nodeID, file_path, nodetype=node_type, category=category) + txfile = self.txmanager.get_txfile_from_id(nodeID) + try: + bpy.ops.rman_txmgr_list.add_texture('EXEC_DEFAULT', filepath=file_path, nodeID=nodeID) + except RuntimeError: + pass + txmake_all(blocking=False) + if txfile: + self.done_callback(nodeID, txfile) + if txfile: return (txfile.state == txmanager.STATE_IS_TEX) return False @@ -216,61 +266,61 @@ def update_texture(node, ob=None, check_exists=False): return prop_meta = getattr(node, 'prop_meta', dict()) - for prop_name, meta in prop_meta.items(): + textured_params = getattr(node, 'rman_textured_params', list()) + for prop_name in textured_params: + meta = prop_meta.get(prop_name, dict()) bl_prop_info = BlPropInfo(node, prop_name, meta) - if not bl_prop_info.prop: - continue - if bl_prop_info.renderman_type == 'page': - continue - elif bl_prop_info.is_texture: - node_type = '' - if isinstance(ob, bpy.types.Object): - if ob.type == 'LIGHT': - node_type = ob.data.renderman.get_light_node_name() - else: - node_type = node.bl_label + node_type = '' + if isinstance(ob, bpy.types.Object): + if ob.type == 'LIGHT': + node_type = ob.data.renderman.get_light_node_name() else: - node_type = node.bl_label - - if ob and check_exists: - nodeID = generate_node_id(node, prop_name, ob=ob) - if get_txmanager().does_nodeid_exists(nodeID): - continue + node_type = node.bl_label + else: + node_type = node.bl_label - category = getattr(node, 'renderman_node_type', 'pattern') - get_txmanager().add_texture(node, ob, prop_name, bl_prop_info.prop, node_type=node_type, category=category) + if ob and check_exists: + nodeID = generate_node_id(node, prop_name, ob=ob) + if get_txmanager().does_nodeid_exists(nodeID): + continue -def generate_node_id(node, prop_name, ob=None): - if node is None: - nodeID = '%s|%s' % (prop_name, ob.name) - return nodeID + category = getattr(node, 'renderman_node_type', 'pattern') + get_txmanager().add_texture(node, ob, prop_name, bl_prop_info.prop, node_type=node_type, category=category) + +def generate_node_name(node, prop_name, ob=None, nm=None): + node_name = '' + if not nm: + nm = shadergraph_utils.get_nodetree_name(node) + if nm: + node_name = '%s|%s|' % (nm, node.name) + elif ob: + node_name = '%s|%s|' % (ob.name, node.name) - nm = shadergraph_utils.get_nodetree_name(node) - if nm: - nodeID = '%s|%s|%s' % (node.name, prop_name, nm) - - else: + if node_name == '': prop = '' real_file = '' if hasattr(node, prop_name): prop = getattr(node, prop_name) real_file = filepath_utils.get_real_path(prop) - nodeID = '%s|%s|%s' % (node.name, prop_name, real_file) - return nodeID + node_name = '%s|%s|' % (node.name, real_file) + return node_name + +def generate_node_id(node, prop_name, ob=None): + node_name = generate_node_name(node, prop_name, ob=ob) + plug_uuid = get_txmanager().txmanager.get_plug_id(node_name, prop_name) + return plug_uuid def get_textures(id, check_exists=False, mat=None): if id is None or not id.node_tree: return - nt = id.node_tree ob = id if mat: ob = mat - for node in nt.nodes: - if node.bl_idname == 'ShaderNodeGroup': - get_textures(node, check_exists=check_exists, mat=ob) - else: - update_texture(node, ob=ob, check_exists=check_exists) + nodes_list = list() + shadergraph_utils.gather_all_textured_nodes(ob, nodes_list) + for node in nodes_list: + update_texture(node, ob=ob, check_exists=check_exists) def get_blender_image_path(bl_image): if bl_image.packed_file: @@ -291,24 +341,7 @@ def add_images_from_image_editor(): bpy.ops.rman_txmgr_list.add_texture('EXEC_DEFAULT', filepath=img_path, nodeID=nodeID) if txfile: get_txmanager().done_callback(nodeID, txfile) - -def add_openvdb(ob): - db = ob.data - grids = db.grids - grids.load() - openvdb_file = filepath_utils.get_real_path(db.filepath) - if db.is_sequence: - grids.load() - # if we have a sequence, get the current frame filepath and - # then substitute with - openvdb_file = string_utils.get_tokenized_openvdb_file(grids.frame_filepath, grids.frame) - - if openvdb_file: - input_name = 'filepath' - nodeID = generate_node_id(None, input_name, ob=ob) - get_txmanager().txmanager.add_texture(nodeID, openvdb_file, category='openvdb') - bpy.ops.rman_txmgr_list.add_texture('EXEC_DEFAULT', filepath=openvdb_file, nodeID=nodeID) - + def parse_scene_for_textures(bl_scene=None): #add_images_from_image_editor() @@ -325,8 +358,6 @@ def parse_scene_for_textures(bl_scene=None): node = o.data.renderman.get_light_node() if node: update_texture(node, ob=o) - #elif o.type == 'VOLUME': - # add_openvdb(o) for world in bpy.data.worlds: if not world.use_nodes: @@ -354,7 +385,10 @@ def save_scene_state(state): scene = bpy.context.scene rm = getattr(scene, 'renderman', None) if rm: - setattr(rm, 'txmanagerData', state) + try: + setattr(rm, 'txmanagerData', state) + except AttributeError: + pass def load_scene_state(): """Load the JSON serialization from scene.renderman.txmanagerData and use it @@ -388,10 +422,40 @@ def txmanager_pre_save_cb(bl_scene): 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 check_node_rename(id): + nodes_list = list() + ob = None + if isinstance(id, bpy.types.Material): + ob = id.original + shadergraph_utils.gather_all_textured_nodes(ob, nodes_list) + elif isinstance(id, bpy.types.Object): + ob = id.original + if id.type == 'CAMERA': + node = shadergraph_utils.find_projection_node(ob) + if node: + nodes_list.append(node) + elif id.type == 'LIGHT': + shadergraph_utils.gather_all_textured_nodes(ob, nodes_list) + + for node in nodes_list: + txm_id = getattr(node, 'txm_id', None) + node_name = generate_node_name(node, '', nm=ob.name) + # check if the txm_id property matches what we think the node name + # should be + if txm_id != node_name: + # txm_id differs, update the texture manager + update_texture(node, ob=ob, check_exists=False) def link_file_handler(id): if isinstance(id, bpy.types.Material): @@ -404,9 +468,11 @@ def link_file_handler(id): update_texture(node, ob=id, check_exists=True) elif id.type == 'LIGHT': - node = id.data.renderman.get_light_node() - if node: - update_texture(node, ob=id, check_exists=True) + nodes_list = list() + ob = id.original + shadergraph_utils.gather_all_textured_nodes(ob, nodes_list) + for node in nodes_list: + update_texture(node, ob=ob, check_exists=True) def txmake_all(blocking=True): get_txmanager().txmake_all(blocking=blocking) \ No newline at end of file diff --git a/rman_bl_nodes/__init__.py b/rman_bl_nodes/__init__.py index 3fbde510..22840da6 100644 --- a/rman_bl_nodes/__init__.py +++ b/rman_bl_nodes/__init__.py @@ -453,6 +453,19 @@ def free(self): ntype.__annotations__['plugin_name'] = StringProperty(name='Plugin Name', default=name, options={'HIDDEN'}) + has_textured_params = False + rman_textured_params = list() + if node_desc.textured_params: + has_textured_params = True + ntype.__annotations__['txm_id'] = StringProperty(name='txm_id', default="") + for param in node_desc.textured_params: + rman_textured_params.append(param.name) + + setattr(ntype, 'rman_textured_params', rman_textured_params) + ntype.__annotations__['rman_has_textured_params'] = BoolProperty( + name="rman_has_textured_params", + default=has_textured_params) + class_generate_properties(ntype, name, node_desc) if nodeType == 'light': ntype.__annotations__['light_primary_visibility'] = BoolProperty( @@ -499,6 +512,14 @@ def free(self): osl_node_type.__annotations__['plugin_name'] = StringProperty(name='Plugin Name', default=name, options={'HIDDEN'}) + + if has_textured_params: + osl_node_type.__annotations__['txm_id'] = StringProperty(name='txm_id', default="") + + setattr(osl_node_type, 'rman_textured_params', rman_textured_params) + osl_node_type.__annotations__['rman_has_textured_params'] = BoolProperty( + name="rman_has_textured_params", + default=has_textured_params) class_generate_properties(osl_node_type, name, node_desc) bpy.utils.register_class(osl_node_type) diff --git a/rman_bl_nodes/rman_bl_nodes_sockets.py b/rman_bl_nodes/rman_bl_nodes_sockets.py index 5d233143..b1c0db42 100644 --- a/rman_bl_nodes/rman_bl_nodes_sockets.py +++ b/rman_bl_nodes/rman_bl_nodes_sockets.py @@ -13,6 +13,8 @@ def update_func(self, context): node = self.node if hasattr(self, 'node') else self __CYCLES_GROUP_NODES__ = ['ShaderNodeGroup', 'NodeGroupInput', 'NodeGroupOutput'] +__SOCKET_HIDE_VALUE__ = ['bxdf', 'projection', 'light', 'integrator', 'struct', 'vstruct' + 'samplefilter', 'displayfilter'] # list for socket registration @@ -242,6 +244,7 @@ def draw_value(self, context, layout, node): def draw(self, context, layout, node, text): + renderman_type = getattr(self, 'renderman_type', '') if self.hide and self.hide_value: pass elif self.hide_value: @@ -251,6 +254,8 @@ def draw(self, context, layout, node, text): elif node.bl_idname in __CYCLES_GROUP_NODES__ or node.bl_idname == "PxrOSLPatternNode": layout.prop(self, 'default_value', text=self.get_pretty_name(node), slider=True) + elif renderman_type in __SOCKET_HIDE_VALUE__: + layout.label(text=self.get_pretty_name(node)) elif hasattr(node, self.name): layout.prop(node, self.name, text=self.get_pretty_name(node), slider=True) diff --git a/rman_constants.py b/rman_constants.py index 797122a5..664456d6 100644 --- a/rman_constants.py +++ b/rman_constants.py @@ -2,7 +2,7 @@ import bpy RFB_ADDON_VERSION_MAJOR = 24 -RFB_ADDON_VERSION_MINOR = 3 +RFB_ADDON_VERSION_MINOR = 4 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) @@ -26,7 +26,7 @@ BLENDER_PYTHON_VERSION = "3.9" RMAN_SUPPORTED_VERSION_MAJOR = 24 -RMAN_SUPPORTED_VERSION_MINOR = 3 +RMAN_SUPPORTED_VERSION_MINOR = 4 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) diff --git a/rman_render.py b/rman_render.py index f3007cec..691529da 100644 --- a/rman_render.py +++ b/rman_render.py @@ -14,6 +14,7 @@ import subprocess import ctypes import numpy +import traceback # for viewport buckets import gpu @@ -526,6 +527,7 @@ def start_render(self, depsgraph, for_background=False): self.rman_is_live_rendering = True 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 @@ -683,6 +685,7 @@ def start_external_render(self, depsgraph): 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 @@ -715,6 +718,7 @@ def start_external_render(self, depsgraph): 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 @@ -775,6 +779,7 @@ def start_bake_render(self, depsgraph, for_background=False): self.start_stats_thread() 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 @@ -854,6 +859,7 @@ def start_external_bake_render(self, depsgraph): rfb_log().info("Finished parsing scene. Total time: %s" % string_utils._format_time_(time.time() - time_start)) 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 @@ -947,6 +953,7 @@ def start_interactive_render(self, context, depsgraph): return True except Exception as e: bpy.ops.renderman.printer('INVOKE_DEFAULT', level="ERROR", message='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 @@ -1053,6 +1060,7 @@ def start_export_rib_selected(self, context, rib_path, export_materials=True, ex self.sg_scene.Render("rib " + rib_output + " -archive") 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 @@ -1079,7 +1087,8 @@ def stop_render(self, stop_draw_thread=True): self.rman_running = False self.rman_interactive_running = False self.rman_swatch_render_running = False - self.rman_is_viewport_rendering = False + self.rman_is_viewport_rendering = False + self.rman_is_exporting = False # Remove callbacks ec = rman.EventCallbacks.Get() diff --git a/rman_scene_sync.py b/rman_scene_sync.py index 535edc15..b6a3f4fb 100644 --- a/rman_scene_sync.py +++ b/rman_scene_sync.py @@ -618,7 +618,6 @@ def update_scene(self, context, depsgraph): self.rman_scene.context = context particle_settings_node = None - did_mesh_update = False # did the mesh actually update prev_num_instances = self.rman_scene.num_object_instances # the number of instances previously # Check the number of instances. If we differ, an object may have been @@ -668,7 +667,6 @@ def update_scene(self, context, depsgraph): elif isinstance(obj.id, bpy.types.Mesh): rfb_log().debug("Mesh updated: %s" % obj.id.name) - did_mesh_update = True ''' # 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 @@ -846,10 +844,6 @@ def update_scene(self, context, depsgraph): self.update_particles.add(obj.id) if not self.num_instances_changed: - if rman_type == 'MESH' and not did_mesh_update: - # if a mesh didn't actually update don't call obj_geometry_updated - rfb_log().debug("Skip object updated: %s" % obj.id.name) - continue self._obj_geometry_updated(obj) elif isinstance(obj.id, bpy.types.Collection): @@ -1108,10 +1102,11 @@ def texture_updated(self, nodeID): if nodeID == '': return tokens = nodeID.split('|') - if len(tokens) < 3: + if len(tokens) < 2: return - node_name,param,ob_name = tokens + ob_name = tokens[0] + node_name = tokens[1] node, ob = scene_utils.find_node_by_name(node_name, ob_name) if ob == None: return diff --git a/rman_stats/__init__.py b/rman_stats/__init__.py index 32c3bdd7..bba7724e 100644 --- a/rman_stats/__init__.py +++ b/rman_stats/__init__.py @@ -190,14 +190,13 @@ def init_stats_session(self): def update_session_config(self): - self.web_socket_enabled = prefs_utils.get_pref('rman_roz_webSocketServer', default=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) config_dict = dict() config_dict["logLevel"] = int(prefs_utils.get_pref('rman_roz_logLevel', default='3')) - config_dict["grpcServer"] = prefs_utils.get_pref('rman_roz_grpcServer', default=True) config_dict["webSocketPort"] = self.web_socket_port - config_dict["webSocketServer"] = self.web_socket_enabled + config_dict["liveStatsEnabled"] = self.web_socket_enabled config_str = json.dumps(config_dict) self.rman_stats_session_config.Update(config_str) @@ -320,7 +319,7 @@ def update_payloads(self): if name == "/system.processMemory": # Payload has 3 floats: max, resident, XXX # Convert resident mem to MB : payload[1] / 1024*1024; - memPayload = dat["payload"].split(',') + memPayload = dat["payload"] maxresMB = ((float)(memPayload[1])) / __oneK2__ # Set consistent fixed point output in string @@ -349,7 +348,7 @@ def update_payloads(self): self._prevTotalRaysValid = True self._prevTotalRays = currentTotalRays elif name == "/rman@iterationComplete": - itr = eval(dat['payload'])[0] + itr = dat['payload'][0] self._iterations = itr self.render_live_stats[label] = '%d / %d' % (itr, self._maxSamples) elif name == "/rman/renderer@progress": diff --git a/rman_ui/rman_ui_txmanager.py b/rman_ui/rman_ui_txmanager.py index fcf6ba91..e4cd92ed 100644 --- a/rman_ui/rman_ui_txmanager.py +++ b/rman_ui/rman_ui_txmanager.py @@ -244,11 +244,15 @@ def execute(self, context): tokens = nodeID.split('|') if len(tokens) < 3: - continue - node_name,param,ob_name = tokens + return + + ob_name = tokens[0] + node_name = tokens[1] + param = tokens[2][1:] + node, ob = scene_utils.find_node_by_name(node_name, ob_name) - node, ob = scene_utils.find_node_by_name(node_name, ob_name) if not node: + nodeIDs.append(nodeID) continue if getattr(node, param) != item.name: nodeIDs.append(nodeID) @@ -430,12 +434,25 @@ def execute(self, context): item = None # check if nodeID already exists in the list - for idx, i in enumerate(context.scene.rman_txmgr_list): + nodeIDs = list() + rman_txmgr_list = context.scene.rman_txmgr_list + for idx, i in enumerate(rman_txmgr_list): if i.nodeID == self.nodeID: item = i break + txfile_item = texture_utils.get_txmanager().txmanager.get_txfile_from_id(i.nodeID) + if txfile_item is None: + nodeIDs.append(i.nodeID) + continue + + if txfile_item == txfile and i.nodeID != self.nodeID: + nodeIDs.append(i.nodeID) + + for nodeID in nodeIDs: + bpy.ops.rman_txmgr_list.remove_texture('EXEC_DEFAULT', nodeID=nodeID) + if not item: - item = context.scene.rman_txmgr_list.add() + item = rman_txmgr_list.add() item.nodeID = self.nodeID item.name = txfile.input_image params = txfile.params