From 2f1ae70e0a90ac5d856511b2bdf03462e39da68e Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 9 Dec 2021 09:27:20 -0800 Subject: [PATCH 01/16] Checkpoint for RMAN-18594. * Add the new callback methods needed for the core TxManager * Add a couple of properties to nodes to distinguish which ones have textured parameters and their param names. * Extend the despgraph handler to check for node renames. * Modify the add_texture opeartor to try and remove any duplicates. --- rfb_utils/property_utils.py | 3 +- rfb_utils/shadergraph_utils.py | 29 +++- rfb_utils/texture_utils.py | 267 +++++++++++++++++++-------------- rman_bl_nodes/__init__.py | 13 ++ rman_scene_sync.py | 5 +- rman_ui/rman_ui_txmanager.py | 27 +++- 6 files changed, 221 insertions(+), 123 deletions(-) diff --git a/rfb_utils/property_utils.py b/rfb_utils/property_utils.py index d9db2463..4583e914 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' diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index 5ae9e72e..e0462953 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -379,13 +379,34 @@ 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 diff --git a/rfb_utils/texture_utils.py b/rfb_utils/texture_utils.py index 9aac1c7a..752eb1f0 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,27 @@ 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: + 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 +165,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,24 +180,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) + # lookup the txmanager for an already converted texture + txfile = self.txmanager.get_txfile_from_id(plug_uuid) + if txfile is None: + 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 @@ -215,61 +264,57 @@ 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 - - nm = shadergraph_utils.get_nodetree_name(node) - if nm: - nodeID = '%s|%s|%s' % (node.name, prop_name, nm) + 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): + if not nm: + nm = shadergraph_utils.get_nodetree_name(node) + if nm: + node_name = '%s|%s|' % (nm, node.name) else: 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: @@ -290,24 +335,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() @@ -324,8 +352,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: @@ -353,7 +379,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 @@ -386,23 +415,39 @@ def txmanager_pre_save_cb(bl_scene): def depsgraph_handler(bl_scene): for update in bpy.context.evaluated_depsgraph_get().updates: id = update.id + # check new linked in materials if id.library: link_file_handler(id) continue - if isinstance(id, bpy.types.Object): - if id.type == 'VOLUME': - continue - ''' - vol = id.data - ob = id - txfile = get_txmanager().get_txfile_for_vdb(ob) - if txfile: - grids = vol.grids - grids.load() - openvdb_file = string_utils.get_tokenized_openvdb_file(grids.frame_filepath, grids.frame) - if txfile.input_image != openvdb_file: - add_openvdb(ob) - ''' + # 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) + 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): @@ -415,9 +460,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 4a5a9d8f..01b79db0 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( diff --git a/rman_scene_sync.py b/rman_scene_sync.py index 535edc15..e7412f63 100644 --- a/rman_scene_sync.py +++ b/rman_scene_sync.py @@ -1108,10 +1108,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_ui/rman_ui_txmanager.py b/rman_ui/rman_ui_txmanager.py index 06715858..aecf4ced 100644 --- a/rman_ui/rman_ui_txmanager.py +++ b/rman_ui/rman_ui_txmanager.py @@ -242,11 +242,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) @@ -428,12 +432,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 From 105b12d3c2809abd898df0b6baf897a7027f4836 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 9 Dec 2021 09:28:20 -0800 Subject: [PATCH 02/16] Bump the version to 24.4 --- __init__.py | 2 +- rman_constants.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 7ea27bc1..d728d9e7 100644 --- a/__init__.py +++ b/__init__.py @@ -35,7 +35,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/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) From 00fc3c9442319fe832e0ca8e6ad3f433d3a5a10b Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 9 Dec 2021 10:28:08 -0800 Subject: [PATCH 03/16] Add the new texture properties to the backwards compatible OSL pattern nodes. --- rman_bl_nodes/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rman_bl_nodes/__init__.py b/rman_bl_nodes/__init__.py index 01b79db0..261ad2b6 100644 --- a/rman_bl_nodes/__init__.py +++ b/rman_bl_nodes/__init__.py @@ -512,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) From 79ed9e3b00d848d114ee37dda42e3356f7fb6310 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 6 Jan 2022 12:15:32 -0800 Subject: [PATCH 04/16] Don't try to add a texture to the texture manager when the file_path is an empty string. --- rfb_utils/texture_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfb_utils/texture_utils.py b/rfb_utils/texture_utils.py index 752eb1f0..2587dec9 100644 --- a/rfb_utils/texture_utils.py +++ b/rfb_utils/texture_utils.py @@ -141,6 +141,8 @@ def get_output_tex_from_path(self, node, param_name, file_path, ob=None): # 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 From ed837be7544136ec1faf88f846ef38238c0d517e Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 3 Feb 2022 10:37:20 -0800 Subject: [PATCH 05/16] When we add a texture to the texture manager, and a txfile already exists, double check if the input image is different. --- rfb_utils/texture_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfb_utils/texture_utils.py b/rfb_utils/texture_utils.py index 89adf928..760b3b99 100644 --- a/rfb_utils/texture_utils.py +++ b/rfb_utils/texture_utils.py @@ -193,13 +193,13 @@ def add_texture(self, node, ob, param_name, file_path, node_type='PxrTexture', c else: # lookup the txmanager for an already converted texture txfile = self.txmanager.get_txfile_from_id(plug_uuid) - if txfile is None: + 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) + self.done_callback(plug_uuid, txfile) def is_file_src_tex(self, node, prop_name): id = scene_utils.find_node_owner(node) From bff89d2b79f894dd25dead77ec3fbf64a38db492 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Fri, 4 Feb 2022 07:58:48 -0800 Subject: [PATCH 06/16] Fix issue with check_node_rename. Cameras might not have projection nodes attached. --- rfb_utils/texture_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfb_utils/texture_utils.py b/rfb_utils/texture_utils.py index 760b3b99..a669d523 100644 --- a/rfb_utils/texture_utils.py +++ b/rfb_utils/texture_utils.py @@ -438,7 +438,8 @@ def check_node_rename(id): ob = id.original if id.type == 'CAMERA': node = shadergraph_utils.find_projection_node(ob) - nodes_list.append(node) + if node: + nodes_list.append(node) elif id.type == 'LIGHT': shadergraph_utils.gather_all_textured_nodes(ob, nodes_list) From 6e9f485fd2c9b4306846eacfa52f83b564aedb07 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Sun, 6 Feb 2022 11:48:27 -0800 Subject: [PATCH 07/16] Make sure to mark rman_is_exporting is False, otherwise the stats thread will get stuck. --- rman_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rman_render.py b/rman_render.py index 6f7a9675..640f06f2 100644 --- a/rman_render.py +++ b/rman_render.py @@ -1077,7 +1077,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() From 1ea6a960f577dc1d81b838f08ee6d268edb4489b Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 24 Feb 2022 14:06:46 -0800 Subject: [PATCH 08/16] Add some fallback code when we can't ascertain the nodetree name when coming up with a node name for the texture manager. --- rfb_utils/shadergraph_utils.py | 10 +++++----- rfb_utils/texture_utils.py | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index e0462953..c03059a5 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -409,26 +409,26 @@ def gather_all_textured_nodes(ob, nodes_list): 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.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 980181fd..52006e0f 100644 --- a/rfb_utils/texture_utils.py +++ b/rfb_utils/texture_utils.py @@ -288,11 +288,15 @@ def update_texture(node, ob=None, check_exists=False): 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) - else: + elif ob: + node_name = '%s|%s|' % (ob.name, node.name) + + if node_name == '': prop = '' real_file = '' if hasattr(node, prop_name): From 26ac2866bf9a9c42c30db513a14374aad6a8eb6a Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Wed, 2 Mar 2022 11:08:18 -0800 Subject: [PATCH 09/16] Update get_nodetree_name to check if node_tree is None on a light. --- rfb_utils/shadergraph_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index c03059a5..5fa43443 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -428,6 +428,8 @@ def get_nodetree_name(node): for ob in bpy.data.objects: if ob.type == 'LIGHT': light = ob.data + if light.node_tree is None: + continue if light.node_tree.original == nt: return ob.name elif ob.type == 'CAMERA': From 70a5f08937c6cf98a2018f4b97c998f19f35612b Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Wed, 23 Mar 2022 10:16:05 -0700 Subject: [PATCH 10/16] Fix issue with setting params that are float2. We need to consider these arrays, even though the type is not an array. --- rfb_utils/property_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rfb_utils/property_utils.py b/rfb_utils/property_utils.py index 4583e914..810f9e8b 100644 --- a/rfb_utils/property_utils.py +++ b/rfb_utils/property_utils.py @@ -771,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 From ab615d86c76460d59e0efa94d48e56cf98181354 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 24 Mar 2022 07:45:18 -0700 Subject: [PATCH 11/16] Emit the traceback into the log when exporting fails. --- rman_render.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rman_render.py b/rman_render.py index a70a9bf8..76eef609 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 @@ -525,6 +526,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 @@ -682,6 +684,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 @@ -714,6 +717,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 @@ -774,6 +778,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 @@ -853,6 +858,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 @@ -946,6 +952,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 @@ -1052,6 +1059,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 From cb343fd7354d8239094a974688917a13fa107203 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 24 Mar 2022 09:46:11 -0700 Subject: [PATCH 12/16] * The format of some stats payloads have changed * Set the default for "Enable Live Stats" in the preferences to True --- preferences.py | 2 +- rman_stats/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/preferences.py b/preferences.py index 965348f8..31163b13 100644 --- a/preferences.py +++ b/preferences.py @@ -506,7 +506,7 @@ def update_stats_config(self, context): 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.", update=update_stats_config) - rman_roz_webSocketServer: BoolProperty(name="Enable Live Stats", default=False, + rman_roz_webSocketServer: 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, diff --git a/rman_stats/__init__.py b/rman_stats/__init__.py index 32c3bdd7..71bf6aae 100644 --- a/rman_stats/__init__.py +++ b/rman_stats/__init__.py @@ -320,7 +320,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 +349,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": From 026d0d9fb8741f159a95620b77a4ea5a2c10d5e8 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Thu, 24 Mar 2022 14:15:04 -0700 Subject: [PATCH 13/16] Only draw the label name when dealing with bxdf sockets. --- rman_bl_nodes/rman_bl_nodes_sockets.py | 5 +++++ 1 file changed, 5 insertions(+) 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) From b26d8727f66fde28c64adeb51e7bf30601629084 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Tue, 5 Apr 2022 07:53:34 -0700 Subject: [PATCH 14/16] Add the new 'liveStatsEnabled' pref for 24.4. --- preferences.py | 14 ++++++++------ rman_stats/__init__.py | 5 ++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/preferences.py b/preferences.py index 31163b13..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=True, - description="Turning this off will disable the live statistics system in RfB.", + rman_roz_webSocketServer: BoolProperty(name="Enable Live Stats", default=False, + 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/rman_stats/__init__.py b/rman_stats/__init__.py index 71bf6aae..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) From 375d3cf3944e80f0fefc5eee42b2304bea6b6e4f Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Wed, 20 Apr 2022 12:58:10 -0700 Subject: [PATCH 15/16] Remove the extra check to see if a mesh actually updated in RmanSceneSync. This didn't work when the mesh was changed because of armatures. --- rman_scene_sync.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rman_scene_sync.py b/rman_scene_sync.py index e7412f63..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): From 3d02391d71bf927f9d8a9193e8ee32a3a78f56e5 Mon Sep 17 00:00:00 2001 From: Ian Hsieh Date: Fri, 22 Apr 2022 10:55:56 -0700 Subject: [PATCH 16/16] Update change log. --- changelog.txt | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) 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