diff --git a/__init__.py b/__init__.py index 60d45959..7ea27bc1 100644 --- a/__init__.py +++ b/__init__.py @@ -35,7 +35,7 @@ bl_info = { "name": "RenderMan For Blender", "author": "Pixar", - "version": (24, 2, 0), + "version": (24, 3, 0), "blender": (2, 83, 0), "location": "Info Header, render engine menu", "description": "RenderMan 24 integration", @@ -68,7 +68,11 @@ def __init__(self): return def __del__(self): - from . import rman_render + try: + from . import rman_render + except ModuleNotFoundError: + return + rr = rman_render.RmanRender.get_rman_render() try: if self.is_preview: diff --git a/display_driver/d_blender.cpp b/display_driver/d_blender.cpp index 382b16b4..77b9afba 100644 --- a/display_driver/d_blender.cpp +++ b/display_driver/d_blender.cpp @@ -40,6 +40,11 @@ #include "BlenderOptiXDenoiser.h" #endif +#include + +typedef bool (*FuncPtr)(); +FuncPtr tag_redraw_func; + struct BlenderImage { BlenderImage() @@ -82,6 +87,7 @@ struct BlenderImage const uint8_t* surface; display::RenderOutput::DataType type; size_t noutputs; + std::atomic bufferUpdated; // These two aren't currently used // but are needed if we decide to use a @@ -340,7 +346,7 @@ void DrawBufferToBlender(int viewWidth, int viewHeight) if (blenderImage == nullptr) { - //fprintf(stderr, "d_blender: cannot find first image\n"); + fprintf(stderr, "d_blender: cannot find first image\n"); return; } @@ -360,6 +366,7 @@ void DrawBufferToBlender(int viewWidth, int viewHeight) { if (!blenderImage->framebuffer) { + fprintf(stderr, "Framebuffer not ready\n"); return; } @@ -429,6 +436,36 @@ void DrawBufferToBlender(int viewWidth, int viewHeight) glDeleteTextures(1, &blenderImage->texture_id); } + +PRMANEXPORT +void SetRedrawCallback(bool(*pyfuncobj)()) +{ + tag_redraw_func = pyfuncobj; +} + +PRMANEXPORT +bool HasBufferUpdated() +{ + if (s_blenderImages.empty()) + { + return false; + } + BlenderImage* blenderImage = s_blenderImages[0]; + return blenderImage->bufferUpdated; +} + +PRMANEXPORT +void ResetBufferUpdated() +{ + if (s_blenderImages.empty()) + { + return; + } + + BlenderImage* blenderImage = s_blenderImages[0]; + blenderImage->bufferUpdated = false; +} + } // extern "C" @@ -640,6 +677,14 @@ DspyImageData( } else { blenderImage->useActiveRegion = false; } + + if (tag_redraw_func) + { + if (!tag_redraw_func()) + { + tag_redraw_func = NULL; + } + } return PkDspyErrorNone; } @@ -799,12 +844,18 @@ void DisplayBlender::Close() { std::free(m_image->denoiseFrameBuffer); } + tag_redraw_func = NULL; } void DisplayBlender::Notify(const uint32_t iteration, const uint32_t totaliterations, const NotifyFlags flags, const pxrcore::ParamList& /*metadata*/) { + if (flags != k_notifyIteration && flags != k_notifyRender) + { + return; + } CopyXpuBuffer(m_image); + m_image->bufferUpdated = true; } static void closeBlenderImages() diff --git a/rfb_utils/color_manager_blender.py b/rfb_utils/color_manager_blender.py index d73720eb..0a53d873 100644 --- a/rfb_utils/color_manager_blender.py +++ b/rfb_utils/color_manager_blender.py @@ -8,6 +8,7 @@ ColorManager = None __clrmgr__ = None +__has_warned__ = False class ColorManagerBlender(ColorManager): def __init__(self, config_path, **kwargs): @@ -38,8 +39,16 @@ def init(): def get_env_config_path(): """return ocio config path from the environment """ + global __has_warned__ blender_config_path = envconfig().get_blender_ocio_config() - ociopath = envconfig().getenv('OCIO', blender_config_path) + envconfig_path = envconfig().getenv('OCIO', None) + ociopath = blender_config_path + if envconfig_path: + if os.path.exists(envconfig_path): + ociopath = envconfig_path + elif not __has_warned__: + bpy.ops.renderman.printer('INVOKE_DEFAULT', level='WARNING', message='OCIO environment value (%s) is invalid.' % envconfig_path) + __has_warned__ = True return ociopath def get_config_path(): diff --git a/rfb_utils/display_utils.py b/rfb_utils/display_utils.py index 5bc26ce3..a3b5e854 100644 --- a/rfb_utils/display_utils.py +++ b/rfb_utils/display_utils.py @@ -633,11 +633,6 @@ def get_dspy_dict(rman_scene, expandTokens=True): display_driver = rm.render_into do_optix_denoise = rm.blender_optix_denoiser - # FIXME: remove these lines once we are able to get some kind of progress - # when rendering to XPU - if rm.render_into == 'blender' and scene_utils.get_render_variant(rman_scene.bl_scene) != 'prman': - display_driver = 'openexr' - if rm.render_rman_stylized: _add_stylized_channels(dspys_dict, display_driver, rman_scene, expandTokens) diff --git a/rfb_utils/draw_utils.py b/rfb_utils/draw_utils.py index ac79788f..79bd095d 100644 --- a/rfb_utils/draw_utils.py +++ b/rfb_utils/draw_utils.py @@ -62,7 +62,97 @@ def draw_dsypmeta_item(layout, node, prop_name): layout.prop(item, 'name') layout.prop(item, 'type') layout.prop(item, 'value_%s' % item.type, slider=True) - + +def draw_array_elem(layout, node, prop_name, bl_prop_info, nt, context, level): + row = layout.row(align=True) + row.enabled = not bl_prop_info.prop_disabled + + ui_prop = prop_name + "_uio" + ui_open = getattr(node, ui_prop) + icon = get_open_close_icon(ui_open) + + split = layout.split(factor=NODE_LAYOUT_SPLIT) + row = split.row() + row.enabled = not bl_prop_info.prop_disabled + draw_indented_label(row, None, level) + + row.context_pointer_set("node", node) + op = row.operator('node.rman_open_close_page', text='', icon=icon, emboss=False) + op.prop_name = ui_prop + + sub_prop_names = list(bl_prop_info.prop) + + prop_label = bl_prop_info.label + coll_nm = '%s_collection' % prop_name + collection = getattr(node, coll_nm) + array_len = len(collection) + array_label = prop_label + ' [%d]:' % array_len + row.label(text=array_label) + if ui_open: + level += 1 + row = layout.row(align=True) + col = row.column() + row = col.row() + draw_indented_label(row, None, level) + coll_idx_nm = '%s_collection_index' % prop_name + row.template_list("RENDERMAN_UL_Array_List", "", node, coll_nm, node, coll_idx_nm, rows=5) + col = row.column(align=True) + row = col.row() + row.context_pointer_set("node", node) + op = row.operator('renderman.add_remove_array_elem', icon="ADD", text="") + op.collection = coll_nm + op.collection_index = coll_idx_nm + op.param_name = prop_name + op.action = 'ADD' + op.elem_type = bl_prop_info.renderman_array_type + row = col.row() + row.context_pointer_set("node", node) + op = row.operator('renderman.add_remove_array_elem', icon="REMOVE", text="") + op.collection = coll_nm + op.collection_index = coll_idx_nm + op.param_name = prop_name + op.action = 'REMOVE' + op.elem_type = bl_prop_info.renderman_array_type + + coll_index = getattr(node, coll_idx_nm, None) + if coll_idx_nm is None: + return + + if coll_index > -1 and coll_index < len(collection): + item = collection[coll_index] + row = layout.row(align=True) + socket_name = '%s[%d]' % (prop_name, coll_index) + socket = node.inputs.get(socket_name, None) + if socket and socket.is_linked: + input_node = shadergraph_utils.socket_node_input(nt, socket) + icon = get_open_close_icon(socket.ui_open) + + split = layout.split() + row = split.row() + draw_indented_label(row, None, level) + row.context_pointer_set("socket", socket) + row.operator('node.rman_open_close_link', text='', icon=icon, emboss=False) + rman_icon = rfb_icons.get_node_icon(input_node.bl_label) + row.label(text='Value (%s):' % input_node.name) + + row.context_pointer_set("socket", socket) + row.context_pointer_set("node", node) + row.context_pointer_set("nodetree", nt) + row.menu('NODE_MT_renderman_connection_menu', text='', icon_value=rman_icon.icon_id) + + if socket.ui_open: + draw_node_properties_recursive(layout, context, nt, + input_node, level=level + 1) + + return + + row.prop(item, 'value_%s' % item.type, slider=True) + if socket: + row.context_pointer_set("socket", socket) + row.context_pointer_set("node", node) + row.context_pointer_set("nodetree", nt) + rman_icon = rfb_icons.get_icon('rman_connection_menu') + row.menu('NODE_MT_renderman_connection_menu', text='', icon_value=rman_icon.icon_id) def _draw_ui_from_rman_config(config_name, panel, context, layout, parent): row_dict = dict() @@ -273,58 +363,8 @@ def draw_prop(node, prop_name, layout, level=0, nt=None, context=None, sticky=Fa return elif bl_prop_info.renderman_type == 'array': - row = layout.row(align=True) - row.enabled = not bl_prop_info.prop_disabled - - ui_prop = prop_name + "_uio" - ui_open = getattr(node, ui_prop) - icon = get_open_close_icon(ui_open) - - split = layout.split(factor=NODE_LAYOUT_SPLIT) - row = split.row() - row.enabled = not bl_prop_info.prop_disabled - draw_indented_label(row, None, level) - - row.context_pointer_set("node", node) - op = row.operator('node.rman_open_close_page', text='', icon=icon, emboss=False) - op.prop_name = ui_prop - - sub_prop_names = list(bl_prop_info.prop) - arraylen = getattr(node, '%s_arraylen' % prop_name) - prop_label = bl_prop_info.label - row.label(text=prop_label + ' [%d]:' % arraylen) - - if ui_open: - level += 1 - row = layout.row(align=True) - col = row.column() - row = col.row() - draw_indented_label(row, None, level) - row.prop(node, '%s_arraylen' % prop_name, text='Size') - for i in range(0, arraylen): - row = layout.row(align=True) - col = row.column() - row = col.row() - array_elem_nm = '%s[%d]' % (prop_name, i) - draw_indented_label(row, None, level) - if array_elem_nm in node.inputs: - op_text = '' - socket = node.inputs[array_elem_nm] - row.context_pointer_set("socket", socket) - row.context_pointer_set("node", node) - row.context_pointer_set("nodetree", nt) - - if socket.is_linked: - input_node = shadergraph_utils.socket_node_input(nt, socket) - rman_icon = rfb_icons.get_node_icon(input_node.bl_label) - row.label(text='%s[%d] (%s):' % (prop_label, i, input_node.name)) - row.menu('NODE_MT_renderman_connection_menu', text='', icon_value=rman_icon.icon_id) - draw_node_properties_recursive(layout, context, nt, input_node, level=level + 1) - else: - row.prop(node, '%s[%d]' % (prop_name, i), slider=True) - rman_icon = rfb_icons.get_icon('rman_connection_menu') - row.menu('NODE_MT_renderman_connection_menu', text='', icon_value=rman_icon.icon_id) - return + draw_array_elem(layout, node, prop_name, bl_prop_info, nt, context, level) + return elif bl_prop_info.widget == 'colorramp': node_group = node.rman_fake_node_group_ptr diff --git a/rfb_utils/envconfig_utils.py b/rfb_utils/envconfig_utils.py index 98f499de..14c5dcd5 100644 --- a/rfb_utils/envconfig_utils.py +++ b/rfb_utils/envconfig_utils.py @@ -371,12 +371,18 @@ def _guess_rmantree(): __RMAN_ENV_CONFIG__ = None return None - # check if this version of RenderMan is supported + # check if the major version of RenderMan is supported if buildinfo._version_major < rman_constants.RMAN_SUPPORTED_VERSION_MAJOR: - rfb_log().error("Error loading addon using RMANTREE=%s. RMANTREE must be version %s or greater. Correct RMANTREE setting in addon preferences." % (rmantree, rman_constants.RMAN_SUPPORTED_VERSION_STRING)) + rfb_log().error("Error loading addon using RMANTREE=%s. The major version found (%d) is not supported. Minimum version supported is %s." % (rmantree, buildinfo._version_major, rman_constants.RMAN_SUPPORTED_VERSION_STRING)) __RMAN_ENV_CONFIG__ = None return None + # check if the minor version of RenderMan is supported + 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 + rfb_log().debug("Guessed RMANTREE: %s" % rmantree) # Create an RmanEnvConfig object diff --git a/rfb_utils/filepath_utils.py b/rfb_utils/filepath_utils.py index 2643b394..bb9e4f95 100644 --- a/rfb_utils/filepath_utils.py +++ b/rfb_utils/filepath_utils.py @@ -36,6 +36,7 @@ def view_file(file_path): try: command = opener + " " + file_path os.system(command) + return except Exception as e: rfb_log().error("Open file command failed: %s" % command) pass diff --git a/rfb_utils/generate_property_utils.py b/rfb_utils/generate_property_utils.py index 73dd4c88..20d34329 100644 --- a/rfb_utils/generate_property_utils.py +++ b/rfb_utils/generate_property_utils.py @@ -1,6 +1,7 @@ from ..rman_constants import RFB_ARRAYS_MAX_LEN, __RMAN_EMPTY_STRING__, __RESERVED_BLENDER_NAMES__ from ..rfb_logger import rfb_log from .property_callbacks import * +from ..rman_properties.rman_properties_misc import RendermanArrayGroup from collections import OrderedDict from bpy.props import * from copy import deepcopy @@ -98,11 +99,16 @@ def is_array(ndp): param_name = node_desc_param._name param_label = getattr(node_desc_param, 'label', param_name) + noconnection = False + if hasattr(node_desc_param, 'connectable') and not node_desc_param.connectable: + noconnection = True + prop_meta[param_name] = {'renderman_type': 'array', 'renderman_array_type': node_desc_param.type, 'renderman_name': param_name, 'label': param_label, - 'type': node_desc_param.type + 'type': node_desc_param.type, + '__noconnection': noconnection } prop_names.append(param_name) @@ -113,25 +119,22 @@ def is_array(ndp): ui_label = "%s_uio" % param_name node.__annotations__[ui_label] = BoolProperty(name=ui_label, default=False) sub_prop_names = [] + + coll_nm = '%s_collection' % param_name + prop = CollectionProperty(name=coll_nm, type=RendermanArrayGroup) + node.__annotations__[coll_nm] = prop + + coll_idx_nm = '%s_collection_index' % param_name + prop = IntProperty(name=coll_idx_nm, default=0) + node.__annotations__[coll_idx_nm] = prop + + ## Not used arraylen_nm = '%s_arraylen' % param_name prop = IntProperty(name=arraylen_nm, default=0, min=0, max=RFB_ARRAYS_MAX_LEN, description="Size of array", update=update_array_size_func) node.__annotations__[arraylen_nm] = prop - - for i in range(0, RFB_ARRAYS_MAX_LEN+1): - ndp = deepcopy(node_desc_param) - ndp._name = '%s[%d]' % (param_name, i) - if hasattr(ndp, 'label'): - ndp.label = '%s[%d]' % (ndp.label, i) - ndp.connectable = True - ndp.widget = '' - name, meta, prop = generate_property(node, ndp, update_function=update_function) - meta['renderman_array_name'] = param_name - sub_prop_names.append(ndp._name) - prop_meta[ndp._name] = meta - node.__annotations__[ndp._name] = prop setattr(node, param_name, sub_prop_names) return True diff --git a/rfb_utils/osl_utils.py b/rfb_utils/osl_utils.py index 12ea90db..bbab74f5 100644 --- a/rfb_utils/osl_utils.py +++ b/rfb_utils/osl_utils.py @@ -43,7 +43,10 @@ def readOSO(filePath): elif mdict['name'] == 'match': prop_meta['match'] = mdict['default'] elif mdict['name'] == 'lockgeom': - prop_meta['lockgeom'] = mdict['lockgeom'] + dflt = 1 + lockgeom = mdict.get('default', dflt) + lockgeom = mdict.get('lockgeom', lockgeom) + prop_meta['lockgeom'] = lockgeom shader_meta[name] = prop_meta diff --git a/rfb_utils/property_callbacks.py b/rfb_utils/property_callbacks.py index edc7ea1d..238e9175 100644 --- a/rfb_utils/property_callbacks.py +++ b/rfb_utils/property_callbacks.py @@ -117,6 +117,8 @@ def update_func_with_inputs(self, context): prop_meta = getattr(node, 'prop_meta') if hasattr(node, 'inputs'): for input_name, socket in node.inputs.items(): + if input_name not in prop_meta: + continue if 'hidden' in prop_meta[input_name]: socket.hide = prop_meta[input_name]['hidden'] diff --git a/rfb_utils/property_utils.py b/rfb_utils/property_utils.py index f6408b97..d9db2463 100644 --- a/rfb_utils/property_utils.py +++ b/rfb_utils/property_utils.py @@ -73,7 +73,6 @@ def __init__(self, node, prop_name, prop_meta): self.renderman_type = prop_meta.get('renderman_type', '') self.param_type = self.renderman_type self.arraySize = prop_meta.get('arraySize', None) - self.renderman_array_name = prop_meta.get('renderman_array_name', None) self.renderman_array_type = prop_meta.get('renderman_array_type', '') self.type = prop_meta.get('type', '') self.page = prop_meta.get('page', '') @@ -114,9 +113,6 @@ def is_exportable(self): if not self.is_linked and self.param_type in ['struct', 'enum']: return False - if self.renderman_array_name: - return False - if self.prop_name == 'inputMaterial' or \ (self.vstruct is True) or (self.type == 'vstruct'): return False @@ -329,6 +325,8 @@ def get_output_param_str(rman_sg_node, node, mat_name, socket, to_socket=None, p def is_vstruct_or_linked(node, param): + if param not in node.prop_meta: + return True meta = node.prop_meta[param] if 'vstructmember' not in meta.keys(): if param in node.inputs: @@ -348,37 +346,35 @@ def is_vstruct_or_linked(node, param): # tells if this param has a vstuct connection that is linked and # conditional met - - def is_vstruct_and_linked(node, param): + if param not in node.prop_meta: + return True meta = node.prop_meta[param] if 'vstructmember' not in meta.keys(): return False - else: - vstruct_name, vstruct_member = meta['vstructmember'].split('.') - if node.inputs[vstruct_name].is_linked: - from_socket = node.inputs[vstruct_name].links[0].from_socket - # if coming from a shader group hookup across that - if from_socket.node.bl_idname == 'ShaderNodeGroup': - ng = from_socket.node.node_tree - group_output = next((n for n in ng.nodes if n.bl_idname == 'NodeGroupOutput'), - None) - if group_output is None: - return False - in_sock = group_output.inputs[from_socket.name] - if len(in_sock.links): - from_socket = in_sock.links[0].from_socket - vstruct_from_param = "%s_%s" % ( - from_socket.identifier, vstruct_member) - return vstruct_conditional(from_socket.node, vstruct_from_param) - else: - return False + vstruct_name, vstruct_member = meta['vstructmember'].split('.') + if node.inputs[vstruct_name].is_linked: + from_socket = node.inputs[vstruct_name].links[0].from_socket + # if coming from a shader group hookup across that + if from_socket.node.bl_idname == 'ShaderNodeGroup': + ng = from_socket.node.node_tree + group_output = next((n for n in ng.nodes if n.bl_idname == 'NodeGroupOutput'), + None) + if group_output is None: + return False + + in_sock = group_output.inputs[from_socket.name] + if len(in_sock.links): + from_socket = in_sock.links[0].from_socket + vstruct_from_param = "%s_%s" % ( + from_socket.identifier, vstruct_member) + return vstruct_conditional(from_socket.node, vstruct_from_param) + + return False # gets the value for a node walking up the vstruct chain - - def get_val_vstruct(node, param): if param in node.inputs and node.inputs[param].is_linked: from_socket = node.inputs[param].links[0].from_socket @@ -389,8 +385,6 @@ def get_val_vstruct(node, param): return getattr(node, param) # parse a vstruct conditional string and return true or false if should link - - def vstruct_conditional(node, param): if not hasattr(node, 'shader_meta') and not hasattr(node, 'output_meta'): return False @@ -617,19 +611,19 @@ def set_ramp_rixparams(node, prop_name, prop, param_type, params): interp = 'catmull-rom' params.SetString("%s_Interpolation" % prop_name, interp ) -def set_array_rixparams(node, rman_sg_node, mat_name, bl_prop_info, prop_name, prop, params): - array_len = getattr(node, '%s_arraylen' % prop_name) - sub_prop_names = getattr(node, prop_name) - sub_prop_names = sub_prop_names[:array_len] +def set_array_rixparams(node, rman_sg_node, mat_name, bl_prop_info, prop_name, prop, params): + coll_nm = '%s_collection' % prop_name val_array = [] val_ref_array = [] param_type = bl_prop_info.renderman_array_type - param_name = bl_prop_info.renderman_name - - for nm in sub_prop_names: + param_name = bl_prop_info.renderman_name + collection = getattr(node, coll_nm) + + for i in range(len(collection)): + elem = collection[i] + nm = '%s[%d]' % (prop_name, i) if hasattr(node, 'inputs') and nm in node.inputs and \ node.inputs[nm].is_linked: - to_socket = node.inputs[nm] from_socket = to_socket.links[0].from_socket from_node = to_socket.links[0].from_node @@ -639,16 +633,16 @@ def set_array_rixparams(node, rman_sg_node, mat_name, bl_prop_info, prop_name, p if val: val_ref_array.append(val) else: - prop = getattr(node, nm) + 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) + val_array.append(val) if val_ref_array: 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)) + set_rix_param(params, param_type, param_name, val_array, is_reference=False, is_array=True, array_len=len(val_array)) def set_node_rixparams(node, rman_sg_node, params, ob=None, mat_name=None, group_node=None): # If node is OSL node get properties from dynamic location. diff --git a/rfb_utils/rman_socket_utils.py b/rfb_utils/rman_socket_utils.py index 6c975292..1105a500 100644 --- a/rfb_utils/rman_socket_utils.py +++ b/rfb_utils/rman_socket_utils.py @@ -28,6 +28,20 @@ def find_enable_param(params): if prop_name in __LOBES_ENABLE_PARAMS__: return prop_name +def node_add_input(node, param_type, param_name, meta, param_label): + if param_type not in __RMAN_SOCKET_MAP__: + return + + socket = node.inputs.new( + __RMAN_SOCKET_MAP__[param_type], param_name, identifier=param_label) + socket.link_limit = 1 + + if param_type in ['struct', 'normal', 'vector', 'vstruct', 'void']: + socket.hide_value = True + if param_type == 'struct': + struct_name = meta.get('struct_name', 'Manifold') + socket.struct_name = struct_name + return socket def node_add_inputs(node, node_name, prop_names, first_level=True, label_prefix='', remove=False): ''' Add new input sockets to this ShadingNode @@ -36,14 +50,14 @@ def node_add_inputs(node, node_name, prop_names, first_level=True, label_prefix= for name in prop_names: meta = node.prop_meta[name] param_type = meta['renderman_type'] - param_type = getattr(meta, 'renderman_array_type', param_type) - if name in node.inputs.keys() and remove: - node.inputs.remove(node.inputs[name]) + socket = node.inputs.get(name, None) + if socket: + if remove: + node.inputs.remove(socket) continue - elif name in node.inputs.keys(): - continue - + notconnectable = meta.get('__noconnection', False) + # if this is a page recursively add inputs if param_type == 'page': if first_level and has_lobe_enable_props(node) and name != 'page_Globals': @@ -58,43 +72,35 @@ def node_add_inputs(node, node_name, prop_names, first_level=True, label_prefix= node_add_inputs(node, node_name, getattr(node, name), label_prefix=label + ' ', first_level=False, remove=True) - continue - else: node_add_inputs(node, node_name, getattr(node, name), first_level=first_level, label_prefix=label_prefix, remove=remove) - continue - elif param_type == 'array': - arraylen = getattr(node, '%s_arraylen' % name) - sub_prop_names = getattr(node, name) - sub_prop_names = sub_prop_names[:arraylen] - node_add_inputs(node, node_name, sub_prop_names, - label_prefix='', - first_level=False, remove=False) continue + elif param_type == 'array': + if notconnectable: + continue + coll_nm = '%s_collection' % name + param_array_type = meta.get('renderman_array_type') + + collection = getattr(node, coll_nm) + for i in range(len(collection)): + param_array_name = '%s[%d]' % (name, i) + param_array_label = '%s[%d]' % (name, i) + param_array_label = label_prefix + meta.get('label', name) + '[%d]' % i + if param_array_name in node.inputs.keys(): + if remove: + node.inputs.remove(node.inputs[param_array_name]) + continue + node_add_input(node, param_array_type, param_array_name, meta, param_array_label) - if remove: - continue - # # if this is not connectable don't add socket - if param_type not in __RMAN_SOCKET_MAP__: - continue - if '__noconnection' in meta and meta['__noconnection']: continue + if remove or notconnectable: + continue param_name = name - param_label = label_prefix + meta.get('label', param_name) - socket = node.inputs.new( - __RMAN_SOCKET_MAP__[param_type], param_name, identifier=param_label) - socket.link_limit = 1 - #socket.default_value = meta['default_value'] - - if param_type in ['struct', 'normal', 'vector', 'vstruct', 'void']: - socket.hide_value = True - if param_type == 'struct': - struct_name = meta.get('struct_name', 'Manifold') - socket.struct_name = struct_name + node_add_input(node, param_type, param_name, meta, param_label) update_inputs(node) diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index d3170d07..5ae9e72e 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -438,18 +438,23 @@ def get_group_node(node): return current_group_node -def get_all_shading_nodes(): +def get_all_shading_nodes(scene=None): '''Find all shading nodes in the scene + Arguments: + scene (bpy.types.Scene) - (optional) the scene we want to find the shading nodes in + Returns: (list) - list of all the shading nodes ''' nodes = list() - context = bpy.context - scene = context.scene + if not scene: + context = bpy.context + scene = context.scene + world = scene.world integrator = find_integrator_node(world) @@ -791,17 +796,17 @@ def has_stylized_pattern_node(ob, node=None): prop_meta = node.prop_meta[prop_name] if prop_meta['renderman_type'] == 'array': - array_len = getattr(node, '%s_arraylen' % prop_name) - for i in range(0, array_len): - nm = '%s[%d]' % (prop_name, i) - sub_prop = getattr(node, nm) - if hasattr(node, 'inputs') and nm in node.inputs and \ - node.inputs[nm].is_linked: + coll_nm = '%s_collection' % prop_name + collection = getattr(node, coll_nm) + for i in range(len(collection)): + nm = '%s[%d]' % (prop_name, i) + if hasattr(node, 'inputs') and nm in node.inputs and \ + node.inputs[nm].is_linked: to_socket = node.inputs[nm] from_node = to_socket.links[0].from_node if from_node.bl_label in RMAN_STYLIZED_PATTERNS: - return from_node + return from_node elif node.inputs[prop_name].is_linked: to_socket = node.inputs[prop_name] diff --git a/rfb_utils/upgrade_utils.py b/rfb_utils/upgrade_utils.py index 26cd0819..005b3fef 100644 --- a/rfb_utils/upgrade_utils.py +++ b/rfb_utils/upgrade_utils.py @@ -1,20 +1,48 @@ from .. import rman_constants from ..rfb_utils import shadergraph_utils from ..rfb_logger import rfb_log +from collections import OrderedDict import bpy def upgrade_242(scene): shadergraph_utils.reload_bl_ramps(scene, check_library=False) +def upgrade_243(scene): + for node in shadergraph_utils.get_all_shading_nodes(scene=scene): + for prop_name, meta in node.prop_meta.items(): + param_type = meta['renderman_type'] + if param_type != 'array': + continue + array_len = getattr(node, '%s_arraylen' % prop_name) + coll_nm = '%s_collection' % prop_name + collection = getattr(node, coll_nm) + param_array_type = meta['renderman_array_type'] + for i in range(array_len): + elem = collection.add() + elem.name = '%s[%d]' % (prop_name, len(collection)-1) + elem.type = param_array_type + +__RMAN_SCENE_UPGRADE_FUNCTIONS__ = OrderedDict() + +__RMAN_SCENE_UPGRADE_FUNCTIONS__['24.2'] = upgrade_242 +__RMAN_SCENE_UPGRADE_FUNCTIONS__['24.3'] = upgrade_243 + def upgrade_scene(bl_scene): + global __RMAN_SCENE_UPGRADE_FUNCTIONS__ + if bpy.context.engine != 'PRMAN_RENDER': return for scene in bpy.data.scenes: version = scene.renderman.renderman_version - if version == '' or version < '24.2': - rfb_log().debug('Upgrade scene to 24.2') - upgrade_242(scene) - + if version == '': + # we started adding a renderman_version property in 24.1 + version = '24.1' + + for version_str, fn in __RMAN_SCENE_UPGRADE_FUNCTIONS__.items(): + if version < version_str: + rfb_log().debug('Upgrade scene to %s' % version_str) + fn(scene) + def update_version(bl_scene): if bpy.context.engine != 'PRMAN_RENDER': return diff --git a/rman_bl_nodes/rman_bl_nodes_menus.py b/rman_bl_nodes/rman_bl_nodes_menus.py index 90b7b4d3..4c6e45d0 100644 --- a/rman_bl_nodes/rman_bl_nodes_menus.py +++ b/rman_bl_nodes/rman_bl_nodes_menus.py @@ -189,10 +189,16 @@ def draw_patterns_menu(self, context): socket = context.socket prop_name = socket.name prop = getattr(node, prop_name, None) - prop_meta = node.prop_meta[prop_name] - renderman_type = prop_meta.get('renderman_type', 'pattern') - renderman_type = prop_meta.get('renderman_array_type', renderman_type) - struct_name = prop_meta.get('struct_name', '') + if prop_name in node.prop_meta: + prop_meta = node.prop_meta[prop_name] + renderman_type = prop_meta.get('renderman_type', 'pattern') + renderman_type = prop_meta.get('renderman_array_type', renderman_type) + struct_name = prop_meta.get('struct_name', '') + else: + prop_meta = dict() + renderman_type = getattr(socket, 'renderman_type', 'pattern') + struct_name = '' + terminal_node = None if hasattr(prop_meta, 'vstruct') or prop_name == 'inputMaterial': @@ -432,9 +438,13 @@ def draw_rman(self, context): prop_name = getattr(socket, 'name', '') prop = getattr(node, prop_name, None) if hasattr(node, 'prop_meta'): - prop_meta = node.prop_meta[prop_name] - renderman_type = prop_meta.get('renderman_type', 'pattern') - renderman_type = prop_meta.get('renderman_array_type', renderman_type) + if prop_name in node.prop_meta: + prop_meta = node.prop_meta[prop_name] + renderman_type = prop_meta.get('renderman_type', 'pattern') + renderman_type = prop_meta.get('renderman_array_type', renderman_type) + else: + prop_meta = dict() + renderman_type = getattr(socket, 'renderman_type', 'pattern') else: renderman_type = 'pattern' diff --git a/rman_bl_nodes/rman_bl_nodes_props.py b/rman_bl_nodes/rman_bl_nodes_props.py index 63e8d315..df236b4d 100644 --- a/rman_bl_nodes/rman_bl_nodes_props.py +++ b/rman_bl_nodes/rman_bl_nodes_props.py @@ -282,6 +282,23 @@ def validate_dome_light(self, ob): poll=validate_dome_light, update=update_dome_light_portal) + rman_coneAngleDepth: FloatProperty(name="Cone Angle Depth", + default=5.0, + min=0.0, + max=10.0, + precision=3, + description="The depth of the cone angle drawn in the viewport" + ) + + rman_coneAngleOpacity: FloatProperty(name="Cone Angle Opacity", + default=0.5, + min=0.1, + max=1.0, + precision=3, + description="The opaqueness of the cone angle drawn in the viewport" + ) + + light_primary_visibility: BoolProperty( name="Light Primary Visibility", description="Camera visibility for this light", diff --git a/rman_bl_nodes/rman_bl_nodes_shaders.py b/rman_bl_nodes/rman_bl_nodes_shaders.py index 3cd996c2..355b189d 100644 --- a/rman_bl_nodes/rman_bl_nodes_shaders.py +++ b/rman_bl_nodes/rman_bl_nodes_shaders.py @@ -248,10 +248,46 @@ def draw_nonconnectable_prop(self, context, layout, prop_name, output_node=None, elif bl_prop_info.renderman_type == 'array': row = layout.row(align=True) col = row.column() + row = col.row() + row.enabled = not bl_prop_info.prop_disabled + prop_label = bl_prop_info.label + coll_nm = '%s_collection' % prop_name + collection = getattr(node, coll_nm) + array_len = len(collection) + array_label = prop_label + ' [%d]:' % array_len + row.label(text=array_label) + coll_idx_nm = '%s_collection_index' % prop_name + row.template_list("RENDERMAN_UL_Array_List", "", node, coll_nm, node, coll_idx_nm, rows=5) + col = row.column(align=True) row = col.row() - arraylen = getattr(node, '%s_arraylen' % prop_name) - row.label(text='%s Size' % prop_name) - row.prop(node, '%s_arraylen' % prop_name, text='') + row.context_pointer_set("node", node) + op = row.operator('renderman.add_remove_array_elem', icon="ADD", text="") + op.collection = coll_nm + op.collection_index = coll_idx_nm + op.param_name = prop_name + op.action = 'ADD' + op.elem_type = bl_prop_info.renderman_array_type + row = col.row() + row.context_pointer_set("node", node) + op = row.operator('renderman.add_remove_array_elem', icon="REMOVE", text="") + op.collection = coll_nm + op.collection_index = coll_idx_nm + op.param_name = prop_name + op.action = 'REMOVE' + op.elem_type = bl_prop_info.renderman_array_type + + coll_index = getattr(node, coll_idx_nm, None) + if coll_idx_nm is None: + return + + if coll_index > -1 and coll_index < len(collection): + item = collection[coll_index] + row = layout.row(align=True) + socket_name = '%s[%d]' % (prop_name, coll_index) + socket = node.inputs.get(socket_name, None) + if not socket: + row.prop(item, 'value_%s' % item.type, slider=True) + return split = layout.split(factor=0.95) @@ -546,10 +582,11 @@ def setOslProps(self, prop_names, shader_meta): rman_socket_utils.__RMAN_SOCKET_MAP__[prop_type], prop_name) else: prop_default = shader_meta[prop_name]["default"] - if prop_type == "float": - prop_default = float(prop_default) - elif prop_type == "int": - prop_default = int(float(prop_default)) + if prop_default: + if prop_type == "float": + prop_default = float(prop_default) + elif prop_type == "int": + prop_default = int(float(prop_default)) if prop_type == "matrix": self.inputs.new(rman_socket_utils.__RMAN_SOCKET_MAP__["struct"], prop_name, prop_name) @@ -560,7 +597,8 @@ def setOslProps(self, prop_names, shader_meta): else: input = self.inputs.new(rman_socket_utils.__RMAN_SOCKET_MAP__[shader_meta[prop_name]["type"]], prop_name, identifier=prop_name) - input.default_value = prop_default + if prop_default: + input.default_value = prop_default if prop_type == 'struct' or prop_type == 'point': input.hide_value = True input.renderman_type = prop_type diff --git a/rman_bl_nodes/rman_bl_nodes_sockets.py b/rman_bl_nodes/rman_bl_nodes_sockets.py index fbb527c5..5d233143 100644 --- a/rman_bl_nodes/rman_bl_nodes_sockets.py +++ b/rman_bl_nodes/rman_bl_nodes_sockets.py @@ -5,6 +5,7 @@ from ..rfb_logger import rfb_log from .. import rfb_icons import time +import re # update node during ipr for a socket default_value def update_func(self, context): @@ -254,7 +255,24 @@ def draw(self, context, layout, node, text): layout.prop(node, self.name, text=self.get_pretty_name(node), slider=True) else: - layout.label(text=self.get_pretty_name(node)) + # check if this is an array element + expr = re.compile(r'.*(\[\d+\])') + m = expr.match(self.name) + if m and m.groups(): + group = m.groups()[0] + coll_nm = self.name.replace(group, '') + collection = getattr(node, '%s_collection' % coll_nm) + elem = None + for e in collection: + if e.name == self.name: + elem = e + break + if elem: + layout.prop(elem, 'value_%s' % elem.type, text=elem.name, slider=True) + else: + layout.label(text=self.get_pretty_name(node)) + else: + layout.label(text=self.get_pretty_name(node)) renderman_node_type = getattr(node, 'renderman_node_type', '') if not self.hide and context.region.type == 'UI' and renderman_node_type != 'output': diff --git a/rman_config/config/overrides/rman_properties_LamaSurface.json b/rman_config/config/overrides/rman_properties_LamaSurface.json new file mode 100644 index 00000000..66a9aba2 --- /dev/null +++ b/rman_config/config/overrides/rman_properties_LamaSurface.json @@ -0,0 +1,12 @@ +{ + "name": "LamaSurface.args", + "params": [ + { + "name": "utilityInteger", + "widget": "default", + "default": 0, + "readOnly": true, + "connectable": true + } + ] +} \ No newline at end of file diff --git a/rman_config/config/overrides/rman_properties_PxrDisney.json b/rman_config/config/overrides/rman_properties_PxrDisney.json new file mode 100644 index 00000000..64fd9b65 --- /dev/null +++ b/rman_config/config/overrides/rman_properties_PxrDisney.json @@ -0,0 +1,12 @@ +{ + "name": "PxrDisney.args", + "params": [ + { + "name": "inputAOV", + "widget": "default", + "default": 0, + "readOnly": true, + "connectable": true + } + ] +} \ No newline at end of file diff --git a/rman_config/config/overrides/rman_properties_PxrDisneyBsdf.json b/rman_config/config/overrides/rman_properties_PxrDisneyBsdf.json new file mode 100644 index 00000000..a31dbd8b --- /dev/null +++ b/rman_config/config/overrides/rman_properties_PxrDisneyBsdf.json @@ -0,0 +1,12 @@ +{ + "name": "PxrDisneyBsdf.args", + "params": [ + { + "name": "inputAOV", + "widget": "default", + "default": 0, + "readOnly": true, + "connectable": true + } + ] +} \ No newline at end of file diff --git a/rman_config/config/overrides/rman_properties_PxrLayerSurface.json b/rman_config/config/overrides/rman_properties_PxrLayerSurface.json index c4d253d8..681f488b 100644 --- a/rman_config/config/overrides/rman_properties_PxrLayerSurface.json +++ b/rman_config/config/overrides/rman_properties_PxrLayerSurface.json @@ -12,6 +12,11 @@ "widget": "propSearch", "options": "prop_parent:context.scene.renderman|prop_name:object_groups", "connectable": false - } + }, + { + "name": "utilityPattern", + "connectable": true, + "default": 0 + } ] } \ No newline at end of file diff --git a/rman_config/config/overrides/rman_properties_PxrMatteID.json b/rman_config/config/overrides/rman_properties_PxrMatteID.json new file mode 100644 index 00000000..45ecaa14 --- /dev/null +++ b/rman_config/config/overrides/rman_properties_PxrMatteID.json @@ -0,0 +1,12 @@ +{ + "name": "PxrMatteID.oso", + "params": [ + { + "name": "inputAOV", + "widget": "default", + "default": 0, + "readOnly": true, + "connectable": true + } + ] +} \ No newline at end of file diff --git a/rman_config/config/overrides/rman_properties_PxrSurface.json b/rman_config/config/overrides/rman_properties_PxrSurface.json index 38346ed5..2ffe8854 100644 --- a/rman_config/config/overrides/rman_properties_PxrSurface.json +++ b/rman_config/config/overrides/rman_properties_PxrSurface.json @@ -19,6 +19,11 @@ "options": "prop_parent:context.scene.renderman|prop_name:vol_aggregates", "connectable": false, "default": "" - } + }, + { + "name": "utilityPattern", + "connectable": true, + "default": 0 + } ] } \ No newline at end of file diff --git a/rman_constants.py b/rman_constants.py index 53a68aec..797122a5 100644 --- a/rman_constants.py +++ b/rman_constants.py @@ -2,7 +2,7 @@ import bpy RFB_ADDON_VERSION_MAJOR = 24 -RFB_ADDON_VERSION_MINOR = 2 +RFB_ADDON_VERSION_MINOR = 3 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 = 2 +RMAN_SUPPORTED_VERSION_MINOR = 3 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_operators/rman_operators_collections.py b/rman_operators/rman_operators_collections.py index 1a923f96..907e1dbc 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.rman_socket_utils import node_add_input import bpy @@ -851,6 +852,88 @@ def execute(self, context): return {'FINISHED'} +class PRMAN_OT_Add_Remove_Array_Element(bpy.types.Operator): + bl_idname = 'renderman.add_remove_array_elem' + bl_label = '' + + action: EnumProperty( + name="", + items=[ + ('ADD', 'Add', ''), + ('REMOVE', 'Remove', '')]) + + param_name: StringProperty(name="", default="") + collection: StringProperty(name="", default="") + collection_index: StringProperty(name="", default="") + elem_type: StringProperty(name="", default="") + + @classmethod + def description(cls, context, properties): + info = 'Add a new array element to this array' + if properties.action == 'REMOVE': + info = 'Remove the selected array element from this array' + return info + + def execute(self, context): + + node = context.node + collection = getattr(node, self.collection) + index = getattr(node, self.collection_index) + meta = node.prop_meta[self.param_name] + connectable = True + if '__noconnection' in meta and meta['__noconnection']: + connectable = False + if self.action == 'ADD': + elem = collection.add() + index += 1 + setattr(node, self.collection_index, index) + elem.name = '%s[%d]' % (self.param_name, len(collection)-1) + elem.type = self.elem_type + if connectable: + param_array_label = '%s[%d]' % (meta.get('label', self.param_name), len(collection)-1) + node_add_input(node, self.elem_type, elem.name, meta, param_array_label) + + else: + do_rename = False + idx = -1 + if connectable: + # rename sockets + def update_sockets(socket, name, label): + link = None + from_socket = None + if socket.is_linked: + link = socket.links[0] + from_socket = link.from_socket + node.inputs.remove(socket) + new_socket = node_add_input(node, self.elem_type, name, meta, label) + if not new_socket: + return + if link and from_socket: + nt = node.id_data + nt.links.new(from_socket, new_socket) + + idx = 0 + elem = collection[index] + node.inputs.remove(node.inputs[elem.name]) + for elem in collection: + nm = elem.name + new_name = '%s[%d]' % (self.param_name, idx) + new_label = '%s[%d]' % (meta.get('label', self.param_name), idx) + socket = node.inputs.get(nm, None) + if socket: + update_sockets(socket, new_name, new_label) + idx += 1 + + collection.remove(index) + index -= 1 + setattr(node, self.collection_index, 0) + for i in range(len(collection)): + elem = collection[i] + elem.name = '%s[%d]' % (self.param_name, i) + + return {'FINISHED'} + + classes = [ COLLECTION_OT_add_remove, COLLECTION_OT_add_remove_dspymeta, @@ -867,7 +950,8 @@ def execute(self, context): PRMAN_OT_add_light_link, PRMAN_OT_remove_light_link, PRMAN_OT_light_link_update_illuminate, - PRMAN_OT_light_link_update_objects + PRMAN_OT_light_link_update_objects, + PRMAN_OT_Add_Remove_Array_Element ] def register(): diff --git a/rman_operators/rman_operators_stylized.py b/rman_operators/rman_operators_stylized.py index 287c9501..8dfad75a 100644 --- a/rman_operators/rman_operators_stylized.py +++ b/rman_operators/rman_operators_stylized.py @@ -96,7 +96,10 @@ def attach_pattern(self, context, ob): nt = mat.node_tree output = shadergraph_utils.is_renderman_nodetree(mat) if not output: - bpy.ops.material.rman_add_rman_nodetree('EXEC_DEFAULT', idtype='material', bxdf_Name='PxrSurface') + bpy.ops.object.rman_add_bxdf('EXEC_DEFAULT', bxdf_name='PxrSurface') + mat = object_utils.get_active_material(ob) + nt = mat.node_tree + output = shadergraph_utils.is_renderman_nodetree(mat) socket = output.inputs[0] if not socket.is_linked: @@ -122,17 +125,26 @@ def attach_pattern(self, context, ob): prop_meta = node.prop_meta[prop_name] if prop_meta['renderman_type'] == 'array': + coll_nm = '%s_collection' % prop_name + coll_idx_nm = '%s_collection_index' % prop_name + param_array_type = prop_meta['renderman_array_type'] + override = {'node': node} + bpy.ops.renderman.add_remove_array_elem(override, + 'EXEC_DEFAULT', + action='ADD', + param_name=prop_name, + collection=coll_nm, + collection_index=coll_idx_nm, + elem_type=param_array_type) - array_len = getattr(node, '%s_arraylen' % prop_name) - array_len += 1 - setattr(node, '%s_arraylen' % prop_name, array_len) pattern_node = nt.nodes.new(pattern_node_name) if pattern_settings: for param_name, param_settings in pattern_settings['params'].items(): 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) nt.links.new(pattern_node.outputs['resultAOV'], node.inputs[sub_prop_nm]) diff --git a/rman_presets/rmanAssetsBlender.py b/rman_presets/rmanAssetsBlender.py index b099a74a..10cd028c 100644 --- a/rman_presets/rmanAssetsBlender.py +++ b/rman_presets/rmanAssetsBlender.py @@ -28,13 +28,12 @@ from rman_utils.rman_assets.core import RmanAsset from rman_utils.rman_assets.common.definitions import TrMode, TrStorage, TrSpace, TrType from rman_utils.filepath import FilePath +from rman_utils.rman_assets.common.external_files import ExternalFile import os import os.path import re -import sys -import time -import bpy as mc # just a test +import shutil import bpy import mathutils from math import radians @@ -45,8 +44,9 @@ from ..rfb_utils import object_utils from ..rfb_utils import transform_utils from ..rfb_utils import texture_utils +from ..rfb_utils import color_manager_blender as clr_mgr from ..rfb_utils.prefs_utils import get_pref, get_addon_prefs -from ..rfb_utils.property_utils import __GAINS_TO_ENABLE__, __LOBES_ENABLE_PARAMS__, is_vstruct_and_linked +from ..rfb_utils.property_utils import __GAINS_TO_ENABLE__, __LOBES_ENABLE_PARAMS__, is_vstruct_and_linked, BlPropInfo from ..rfb_logger import rfb_log from ..rman_bl_nodes import __BL_NODES_MAP__, __RMAN_NODE_TYPES__ from ..rman_constants import RMAN_STYLIZED_FILTERS, RFB_FLOAT3, CYCLES_NODE_MAP @@ -347,6 +347,8 @@ def fix_blender_name(name): return name.replace(' ', '').replace('.', '') def set_asset_params(ob, node, nodeName, Asset): + node_type = node.bl_label + # If node is OSL node get properties from dynamic location. if node.bl_idname == "PxrOSLPatternNode": for input_name, input in node.inputs.items(): @@ -366,219 +368,180 @@ def set_asset_params(ob, node, nodeName, Asset): val = string_utils.convert_val(input.default_value, type_hint=prop_type) pdict = {'type': param_type, 'value': val} - Asset.addParam(nodeName, param_name, pdict) + Asset.addParam(nodeName, node_type, param_name, pdict) - else: + return - for prop_name, meta in node.prop_meta.items(): - if node.plugin_name == 'PxrRamp' and prop_name in ['colors', 'positions']: - continue + for prop_name, meta in node.prop_meta.items(): + bl_prop_info = BlPropInfo(node, prop_name, meta) - param_widget = meta.get('widget', 'default') - if param_widget == 'null' and 'vstructmember' not in meta: - continue + if not bl_prop_info.do_export: + continue - else: - prop = getattr(node, prop_name) - # if property group recurse - if meta['renderman_type'] == 'page': - continue - elif prop_name == 'inputMaterial' or \ - ('vstruct' in meta and meta['vstruct'] is True) or \ - ('type' in meta and meta['type'] == 'vstruct'): - continue + param_widget = bl_prop_info.widget + prop = bl_prop_info.prop + param_name = bl_prop_info.renderman_name + param_type = bl_prop_info.renderman_type + is_linked = bl_prop_info.is_linked - # if input socket is linked reference that - elif hasattr(node, 'inputs') and prop_name in node.inputs and \ - node.inputs[prop_name].is_linked: - - if 'arraySize' in meta: - pass - elif 'renderman_array_name' in meta: - continue - - param_type = 'reference %s' % meta['renderman_type'] - param_name = meta['renderman_name'] - - pdict = {'type': param_type, 'value': None} - Asset.addParam(nodeName, param_name, pdict) - - # see if vstruct linked - elif is_vstruct_and_linked(node, prop_name): - val = None - vstruct_name, vstruct_member = meta[ - 'vstructmember'].split('.') - from_socket = node.inputs[ - vstruct_name].links[0].from_socket - - vstruct_from_param = "%s_%s" % ( - from_socket.identifier, vstruct_member) - if vstruct_from_param in from_socket.node.output_meta: - actual_socket = from_socket.node.output_meta[ - vstruct_from_param] - - param_type = 'reference %s' % meta['renderman_type'] - param_name = meta['renderman_name'] - - node_meta = getattr( - node, 'shader_meta') if node.bl_idname == "PxrOSLPatternNode" else node.output_meta - node_meta = node_meta.get(vstruct_from_param) - is_reference = True - if node_meta: - expr = node_meta.get('vstructConditionalExpr') - # check if we should connect or just set a value - if expr: - if expr.split(' ')[0] == 'set': - val = 1 - param_type = meta['renderman_type'] - - pdict = {'type': param_type, 'value': val} - Asset.addParam(nodeName, param_name, pdict) + if is_linked: + param_type = 'reference %s' % meta['renderman_type'] + param_name = meta['renderman_name'] + + pdict = {'type': param_type, 'value': None} + Asset.addParam(nodeName, node_type, param_name, pdict) + + # see if vstruct linked + elif is_vstruct_and_linked(node, prop_name): + val = None + vstruct_name, vstruct_member = bl_prop_info.vstructmember.split('.') + from_socket = node.inputs[ + vstruct_name].links[0].from_socket + + vstruct_from_param = "%s_%s" % ( + from_socket.identifier, vstruct_member) + if vstruct_from_param in from_socket.node.output_meta: + + node_meta = getattr( + node, 'shader_meta') if node.bl_idname == "PxrOSLPatternNode" else node.output_meta + node_meta = node_meta.get(vstruct_from_param) + if node_meta: + expr = node_meta.get('vstructConditionalExpr') + # check if we should connect or just set a value + if expr: + if expr.split(' ')[0] == 'set': + val = 1 + param_type = meta['renderman_type'] + + pdict = {'type': param_type, 'value': val} + Asset.addParam(nodeName, node_type, param_name, pdict) - else: - rfb_log().warning('Warning! %s not found on %s' % - (vstruct_from_param, from_socket.node.name)) + else: + rfb_log().warning('Warning! %s not found on %s' % + (vstruct_from_param, from_socket.node.name)) - # else output rib - else: - # if struct is not linked continue - if meta['renderman_type'] in ['struct', 'enum']: - continue - - param_type = meta['renderman_type'] - param_name = meta['renderman_name'] - val = None - arrayLen = 0 - - # if this is a gain on PxrSurface and the lobe isn't - # enabled - - if node.bl_idname == 'PxrSurfaceBxdfNode' and \ - prop_name in __GAINS_TO_ENABLE__ and \ - not getattr(node, __GAINS_TO_ENABLE__[prop_name]): - val = [0, 0, 0] if meta[ - 'renderman_type'] == 'color' else 0 - - elif meta['renderman_type'] == 'string': - - val = val = string_utils.convert_val(prop, type_hint=meta['renderman_type']) - if param_widget in ['fileinput', 'assetidinput']: - options = meta['options'] - # txmanager doesn't currently deal with ptex - if node.bl_idname == "PxrPtexturePatternNode": - val = string_utils.expand_string(val, display='ptex', asFilePath=True) - # ies profiles don't need txmanager for converting - elif 'ies' in options: - val = string_utils.expand_string(val, display='ies', asFilePath=True) - # this is a texture - elif ('texture' in options) or ('env' in options) or ('imageplane' in options): - 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) - val = tx_val if tx_val != '' else val - elif param_widget == 'assetidoutput': - display = 'openexr' - if 'texture' in meta['options']: - display = 'texture' - val = string_utils.expand_string(val, display='texture', asFilePath=True) - - elif 'renderman_array_name' in meta: - continue - elif meta['renderman_type'] == 'array': - array_len = getattr(node, '%s_arraylen' % prop_name) - sub_prop_names = getattr(node, prop_name) - sub_prop_names = sub_prop_names[:array_len] - val_array = [] - val_ref_array = [] - param_type = '%s[%d]' % (meta['renderman_array_type'], array_len) - - for nm in sub_prop_names: - if hasattr(node, 'inputs') and nm in node.inputs and \ - node.inputs[nm].is_linked: - val_ref_array.append('') - else: - prop = getattr(node, nm) - 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: - pdict = {'type': '%s [%d]' % (param_type, len(val_ref_array)), 'value': None} - Asset.addParam(nodeName, param_name, pdict) - else: - pdict = {'type': param_type, 'value': val_array} - Asset.addParam(nodeName, param_name, pdict) - continue - elif meta['renderman_type'] == 'colorramp': - nt = bpy.data.node_groups[node.rman_fake_node_group] - if nt: - ramp_name = prop - color_ramp_node = nt.nodes[ramp_name] - colors = [] - positions = [] - # double the start and end points - positions.append(float(color_ramp_node.color_ramp.elements[0].position)) - colors.append(color_ramp_node.color_ramp.elements[0].color[:3]) - for e in color_ramp_node.color_ramp.elements: - positions.append(float(e.position)) - colors.append(e.color[:3]) - positions.append( - float(color_ramp_node.color_ramp.elements[-1].position)) - colors.append(color_ramp_node.color_ramp.elements[-1].color[:3]) - - array_size = len(positions) - pdict = {'type': 'int', 'value': array_size} - Asset.addParam(nodeName, prop_name, pdict) - - pdict = {'type': 'float[%d]' % array_size, 'value': positions} - Asset.addParam(nodeName, "%s_Knots" % prop_name, pdict) - - pdict = {'type': 'color[%d]' % array_size, 'value': colors} - Asset.addParam(nodeName, "%s_Colors" % prop_name, pdict) - - rman_interp_map = { 'LINEAR': 'linear', 'CONSTANT': 'constant'} - interp = rman_interp_map.get(color_ramp_node.color_ramp.interpolation,'catmull-rom') - pdict = {'type': 'string', 'value': interp} - Asset.addParam(nodeName, "%s_Interpolation" % prop_name, pdict) - continue - elif meta['renderman_type'] == 'floatramp': - nt = bpy.data.node_groups[node.rman_fake_node_group] - if nt: - ramp_name = prop - float_ramp_node = nt.nodes[ramp_name] - - curve = float_ramp_node.mapping.curves[0] - knots = [] - vals = [] - # double the start and end points - knots.append(curve.points[0].location[0]) - vals.append(curve.points[0].location[1]) - for p in curve.points: - knots.append(p.location[0]) - vals.append(p.location[1]) - knots.append(curve.points[-1].location[0]) - vals.append(curve.points[-1].location[1]) - array_size = len(knots) - - pdict = {'type': 'int', 'value': array_size} - Asset.addParam(nodeName, prop_name, pdict) - - pdict = {'type': 'float[%d]' % array_size, 'value': knots} - Asset.addParam(nodeName, "%s_Knots" % prop_name, pdict) - - pdict = {'type': 'float[%d]' % array_size, 'value': vals} - Asset.addParam(nodeName, "%s_Floats" % prop_name, pdict) - - pdict = {'type': 'string', 'value': 'catmull-rom'} - Asset.addParam(nodeName, "%s_Interpolation" % prop_name, pdict) + # else output rib + else: + val = None + + # if this is a gain on PxrSurface and the lobe isn't + # enabled + if node.bl_idname == 'PxrSurfaceBxdfNode' and \ + prop_name in __GAINS_TO_ENABLE__ and \ + not getattr(node, __GAINS_TO_ENABLE__[prop_name]): + val = [0, 0, 0] if meta[ + 'renderman_type'] == 'color' else 0 + + elif param_type == 'string': + 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) + val = tx_val if tx_val != '' else val + elif param_widget == 'assetidoutput': + display = 'openexr' + if 'texture' in options: + display = 'texture' + val = string_utils.expand_string(val, display=display, asFilePath=True) + + elif param_type == 'array': + val_array = [] + val_ref_array = [] + coll_nm = '%s_collection' % prop_name + collection = getattr(node, coll_nm) + array_len = len(collection) + param_array_type = bl_prop_info.renderman_array_type + param_type = '%s[%d]' % (param_array_type, array_len) - continue + for elem in collection: + nm = elem.name + if hasattr(node, 'inputs') and nm in node.inputs and \ + node.inputs[nm].is_linked: + val_ref_array.append('') else: + prop = getattr(elem, 'value_%s' % param_array_type) + val = string_utils.convert_val(prop, type_hint=param_array_type) + if param_array_type in RFB_FLOAT3: + val_array.extend(val) + else: + val_array.append(val) + if val_ref_array: + pdict = {'type': '%s [%d]' % (param_array_type, len(val_ref_array)), 'value': None} + Asset.addParam(nodeName, node_type, param_name, pdict) + else: + pdict = {'type': param_array_type, 'value': val_array} + Asset.addParam(nodeName, node_type, param_name, pdict) + continue + elif param_type == 'colorramp': + nt = bpy.data.node_groups[node.rman_fake_node_group] + if nt: + ramp_name = prop + color_ramp_node = nt.nodes[ramp_name] + colors = [] + positions = [] + # double the start and end points + positions.append(float(color_ramp_node.color_ramp.elements[0].position)) + colors.append(color_ramp_node.color_ramp.elements[0].color[:3]) + for e in color_ramp_node.color_ramp.elements: + positions.append(float(e.position)) + colors.append(e.color[:3]) + positions.append( + float(color_ramp_node.color_ramp.elements[-1].position)) + colors.append(color_ramp_node.color_ramp.elements[-1].color[:3]) + + array_size = len(positions) + pdict = {'type': 'int', 'value': array_size} + Asset.addParam(nodeName, node_type, prop_name, pdict) + + pdict = {'type': 'float[%d]' % array_size, 'value': positions} + Asset.addParam(nodeName, node_type, "%s_Knots" % prop_name, pdict) + + pdict = {'type': 'color[%d]' % array_size, 'value': colors} + Asset.addParam(nodeName, node_type, "%s_Colors" % prop_name, pdict) + + rman_interp_map = { 'LINEAR': 'linear', 'CONSTANT': 'constant'} + interp = rman_interp_map.get(color_ramp_node.color_ramp.interpolation,'catmull-rom') + pdict = {'type': 'string', 'value': interp} + Asset.addParam(nodeName, node_type, "%s_Interpolation" % prop_name, pdict) + continue + elif param_type == 'floatramp': + nt = bpy.data.node_groups[node.rman_fake_node_group] + if nt: + ramp_name = prop + float_ramp_node = nt.nodes[ramp_name] + + curve = float_ramp_node.mapping.curves[0] + knots = [] + vals = [] + # double the start and end points + knots.append(curve.points[0].location[0]) + vals.append(curve.points[0].location[1]) + for p in curve.points: + knots.append(p.location[0]) + vals.append(p.location[1]) + knots.append(curve.points[-1].location[0]) + vals.append(curve.points[-1].location[1]) + array_size = len(knots) + + pdict = {'type': 'int', 'value': array_size} + Asset.addParam(nodeName, node_type, prop_name, pdict) + + pdict = {'type': 'float[%d]' % array_size, 'value': knots} + Asset.addParam(nodeName, node_type, "%s_Knots" % prop_name, pdict) + + pdict = {'type': 'float[%d]' % array_size, 'value': vals} + Asset.addParam(nodeName, node_type, "%s_Floats" % prop_name, pdict) + + pdict = {'type': 'string', 'value': 'catmull-rom'} + Asset.addParam(nodeName, node_type, "%s_Interpolation" % prop_name, pdict) + + continue + else: + val = string_utils.convert_val(prop, type_hint=param_type) - val = string_utils.convert_val(prop, type_hint=meta['renderman_type']) - - pdict = {'type': param_type, 'value': val} - Asset.addParam(nodeName, param_name, pdict) + pdict = {'type': param_type, 'value': val} + Asset.addParam(nodeName, node_type, param_name, pdict) def set_asset_connections(nodes_list, Asset): for node in nodes_list: @@ -633,7 +596,7 @@ def export_material_preset(mat, nodes_to_convert, renderman_output_node, Asset): infodict['name'] = 'rman__surface' infodict['type'] = 'reference float3' infodict['value'] = None - Asset.addParam(nodeName, 'rman__surface', infodict) + Asset.addParam(nodeName, nodeType, 'rman__surface', infodict) from_node = renderman_output_node.inputs['Bxdf'].links[0].from_node srcPlug = "%s.%s" % (fix_blender_name(from_node.name), 'outColor') @@ -645,7 +608,7 @@ def export_material_preset(mat, nodes_to_convert, renderman_output_node, Asset): infodict['name'] = 'rman__displacement' infodict['type'] = 'reference float3' infodict['value'] = None - Asset.addParam(nodeName, 'rman__displacement', infodict) + Asset.addParam(nodeName, nodeType, 'rman__displacement', infodict) from_node = renderman_output_node.inputs['Displacement'].links[0].from_node srcPlug = "%s.%s" % (fix_blender_name(from_node.name), 'outColor') @@ -695,7 +658,7 @@ def export_material_preset(mat, nodes_to_convert, renderman_output_node, Asset): os.mkdir(shaders_path) shutil.copy(osl_path, out_file) externalosl = True - Asset.processExternalFile(out_file) + Asset.processExternalFile(None, ExternalFile.k_osl, out_file) elif renderman_node_type == '': # check if a cycles node @@ -705,7 +668,8 @@ def export_material_preset(mat, nodes_to_convert, renderman_output_node, Asset): mapping = CYCLES_NODE_MAP[node.bl_idname] cycles_shader_dir = filepath_utils.get_cycles_shader_path() out_file = os.path.join(cycles_shader_dir, '%s.oso' % cycles_shader_dir) - Asset.processExternalFile(out_file) + externalosl = True + Asset.processExternalFile(None, ExternalFile.k_osl, out_file) node_name = fix_blender_name(node.name) shader_name = node.bl_label @@ -838,7 +802,7 @@ def export_displayfilter_nodes(world, nodes, Asset): pdict['type'] = 'string' if pdict['value'].startswith('lpe:'): pdict['value'] = 'color ' + pdict['value'] - Asset.addParam(chan, 'source', pdict) + Asset.addParam(chan, 'rmanDisplayChannel', 'source', pdict) def parse_texture(imagePath, Asset): """Gathers infos from the image header @@ -875,6 +839,20 @@ def export_asset(nodes, atype, infodict, category, cfg, renderPreview='std', convert_to_tex=infodict.get('convert_to_tex', True) ) + # On save, we can get the current color manager to store the config. + color_mgr = clr_mgr.color_manager() + ocio_config = { + 'config': color_mgr.cfg_name, + 'path': color_mgr.config_file_path(), + 'rules': color_mgr.conversion_rules, + 'aliases': color_mgr.aliases + } + rfb_log().debug('ocio_config %s', '=' * 80) + rfb_log().debug(' config = %s', ocio_config['config']) + rfb_log().debug(' path = %s', ocio_config['path']) + rfb_log().debug(' rules = %s', ocio_config['rules']) + Asset.ocio = ocio_config + asset_type = '' hostPrefs = get_host_prefs() if atype == 'nodeGraph': @@ -988,6 +966,7 @@ def setParams(Asset, node, paramsList): knots_param = None colors_param = None floats_param = None + interpolation_param = None if (nm not in rman_ramps) and (nm not in rman_color_ramps): continue @@ -1003,13 +982,20 @@ def setParams(Asset, node, paramsList): colors_param = param elif '_Floats' in pname: floats_param = param + elif '_Interpolation' in pname: + interpolation_param = param if colors_param: n = rman_color_ramps[nm] elements = n.color_ramp.elements size = rman_ramp_size[nm] knots_vals = knots_param.value() - colors_vals = colors_param.value() + colors_vals = colors_param.value() + rman_interp = interpolation_param.value() + + rman_interp_map = { 'bspline':'B_SPLINE' , 'linear': 'LINEAR', 'constant': 'CONSTANT'} + interp = rman_interp_map.get(rman_interp, 'LINEAR') + n.color_ramp.interpolation = interp if len(colors_vals) == size: for i in range(0, size): @@ -1087,10 +1073,28 @@ def setParams(Asset, node, paramsList): if array_len == '': continue array_len = int(array_len) - setattr(node, '%s_arraylen' % pname, array_len) + coll_nm = '%s_collection' % pname + coll_idx_nm = '%s_collection_index' % pname + collection = getattr(node, coll_nm, None) + if collection is None: + continue + elem_type = rman_type + if 'reference' in elem_type: + elem_type = elem_type.replace('reference ', '') + if elem_type == 'integer': + elem_type = 'int' + + for i in range(array_len): + override = {'node': node} + bpy.ops.renderman.add_remove_array_elem(override, + 'EXEC_DEFAULT', + action='ADD', + param_name=pname, + collection=coll_nm, + collection_index=coll_idx_nm, + elem_type=elem_type) pval = param.value() - if pval is None or pval == []: # connected param continue @@ -1099,22 +1103,26 @@ def setParams(Asset, node, paramsList): if rman_type in ['integer', 'float', 'string']: for i in range(0, plen): val = pval[i] - parm_nm = '%s[%d]' % (pname, (i)) - setattr(node, parm_nm, val) + elem = collection[i] + elem.type = elem_type + setattr(node, 'value_%s' % elem.type, val) + # float3 types elif rman_type in float3: j = 1 if isinstance(pval[0], list): for i in range(0, plen): - parm_nm = '%s[%d]' % (pname, (j)) + elem = collection[j] val = (pval[i][0], pval[i][0], pval[i][0]) - setattr(node, parm_nm, val) + elem.type = elem_type + setattr(node, 'value_%s' % elem.type, val) j +=1 else: for i in range(0, plen, 3): - parm_nm = '%s[%d]' % (pname, (j)) + elem = collection[j] val = (pval[i], pval[i+1], pval[i+2]) - setattr(node, parm_nm, val) + elem.type = elem_type + setattr(node, 'value_%s' % elem.type, val) j = j+1 elif pname in node.bl_rna.properties.keys(): @@ -1133,7 +1141,7 @@ def setParams(Asset, node, paramsList): continue if 'string' in ptype: if pval != '': - depfile = Asset.getDependencyPath(pval) + depfile = Asset.getDependencyPath(pname, pval) if depfile: pval = depfile setattr(node, pname, pval) @@ -1216,7 +1224,7 @@ def createNodes(Asset): # loaded through a PxrOSL node. # if PxrOSL is used, we need to find the oso in the asset to # use it in a PxrOSL node. - oso = Asset.getDependencyPath(nodeType + '.oso') + oso = Asset.getDependencyPath(ExternalFile.k_osl, nodeType + '.oso') if oso is None: err = ('createNodes: OSL file is missing "%s"' % nodeType) diff --git a/rman_properties/rman_properties_misc/__init__.py b/rman_properties/rman_properties_misc/__init__.py index c14ddea5..6c02f7a4 100644 --- a/rman_properties/rman_properties_misc/__init__.py +++ b/rman_properties/rman_properties_misc/__init__.py @@ -1,6 +1,6 @@ from bpy.props import PointerProperty, StringProperty, BoolProperty, \ EnumProperty, IntProperty, FloatProperty, FloatVectorProperty, \ - CollectionProperty, BoolVectorProperty + CollectionProperty, BoolVectorProperty, IntVectorProperty from ...rfb_utils import shadergraph_utils from ...rfb_logger import rfb_log @@ -221,8 +221,33 @@ class RendermanReferencePosePrimVars(bpy.types.PropertyGroup): rman__WNref: FloatVectorProperty(name='rman__WNref', default=(0,0, 0), size=3, - subtype="XYZ") + subtype="XYZ") +class RENDERMAN_UL_Array_List(bpy.types.UIList): + + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + layout.label(text=item.name) + +class RendermanArrayGroup(bpy.types.PropertyGroup): + name: StringProperty(name="Name", default="") + type: EnumProperty(name="Type", + items=[ + ('float', 'float', ''), + ('int', 'int', ''), + ('string', 'string', ''), + ('color', 'color', ''), + ('vector', 'vector', ''), + ('normal', 'normal', ''), + ('point', 'point', '') + ]) + + value_float: FloatProperty(name="Value", default=0.0) + value_int: IntProperty(name="Value", default=0) + value_string: StringProperty(name="Value", default="") + value_color: FloatVectorProperty(name="Value", default=(1.0,1.0,1.0), size=3, subtype="COLOR") + value_vector: FloatVectorProperty(name="Value", default=(0.0,0.0,0.0), size=3, subtype="NONE") + 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( @@ -301,7 +326,9 @@ class Tab_CollectionGroup(bpy.types.PropertyGroup): RendermanMeshPrimVar, RendermanReferencePosePrimVars, RendermanAnimSequenceSettings, - Tab_CollectionGroup + Tab_CollectionGroup, + RENDERMAN_UL_Array_List, + RendermanArrayGroup ] def register(): diff --git a/rman_render.py b/rman_render.py index be2f5022..6f7a9675 100644 --- a/rman_render.py +++ b/rman_render.py @@ -57,6 +57,21 @@ def __update_areas__(): for area in window.screen.areas: area.tag_redraw() +def __draw_callback__(): + # callback function for the display driver to call tag_redraw + global __RMAN_RENDER__ + if __RMAN_RENDER__.rman_is_viewport_rendering and __RMAN_RENDER__.bl_engine: + try: + __RMAN_RENDER__.bl_engine.tag_redraw() + pass + except ReferenceError as e: + return False + return True + return False + +DRAWCALLBACK_FUNC = ctypes.CFUNCTYPE(ctypes.c_bool) +__CALLBACK_FUNC__ = DRAWCALLBACK_FUNC(__draw_callback__) + class ItHandler(chatserver.ItBaseHandler): def dspyRender(self): @@ -144,26 +159,38 @@ def draw_threading_func(db): # if there are no 3d viewports, stop IPR db.del_bl_engine() break - if db.rman_is_viewport_rendering: - try: - db.bl_engine.tag_redraw() - time.sleep(refresh_rate) - except ReferenceError as e: - # calling tag_redraw has failed. This might mean - # that there are no more view_3d areas that are shading. Try to - # stop IPR. - #rfb_log().debug("Error calling tag_redraw (%s). Aborting..." % str(e)) - db.del_bl_engine() - return - else: - # rendering to "it" add a 1 second sleep + if db.rman_is_xpu: + if db.has_buffer_updated(): + try: + db.bl_engine.tag_redraw() + db.reset_buffer_updated() + + except ReferenceError as e: + # calling tag_redraw has failed. This might mean + # that there are no more view_3d areas that are shading. Try to + # stop IPR. + #rfb_log().debug("Error calling tag_redraw (%s). Aborting..." % str(e)) + db.del_bl_engine() + return + time.sleep(refresh_rate) + else: time.sleep(1.0) -def call_stats_update_payloads(db): +def call_stats_export_payloads(db): + while db.rman_is_exporting: + db.stats_mgr.update_payloads() + time.sleep(0.1) +def call_stats_update_payloads(db): while db.rman_running: if not db.bl_engine: break + if db.rman_is_xpu and db.is_regular_rendering(): + # stop the render if we are rendering in XPU mode + # and we've reached ~100% + if float(db.stats_mgr._progress) > 98.0: + db.rman_is_live_rendering = False + break db.stats_mgr.update_payloads() time.sleep(0.1) @@ -193,6 +220,12 @@ def render_cb(e, d, db): if db.rman_is_live_rendering: db.rman_is_live_rendering = False +def live_render_cb(e, d, db): + if d == 0: + db.rman_is_refining = False + else: + db.rman_is_refining = True + def preload_xpu(): """On linux there is a problem with std::call_once and blender, by default, being linked with a static libstdc++. @@ -240,6 +273,8 @@ def __init__(self): self.rman_swatch_render_running = False self.rman_is_live_rendering = False self.rman_is_viewport_rendering = False + self.rman_is_xpu = False + self.rman_is_refining = False self.rman_render_into = 'blender' self.rman_license_failed = False self.rman_license_failed_message = '' @@ -388,22 +423,31 @@ def is_regular_rendering(self): return (self.rman_running and not self.rman_interactive_running) def do_draw_buckets(self): - if self.stats_mgr.is_connected() and self.stats_mgr._progress > 99: - return False - return get_pref('rman_viewport_draw_bucket', default=True) + return get_pref('rman_viewport_draw_bucket', default=True) and self.rman_is_refining def do_draw_progressbar(self): - return get_pref('rman_viewport_draw_progress') and self.stats_mgr.is_connected() and self.stats_mgr._progress < 100 + return get_pref('rman_viewport_draw_progress') and self.stats_mgr.is_connected() and self.stats_mgr._progress < 100 + + def start_export_stats_thread(self): + # start an export stats thread + global __RMAN_STATS_THREAD__ + __RMAN_STATS_THREAD__ = threading.Thread(target=call_stats_export_payloads, args=(self, )) + __RMAN_STATS_THREAD__.start() def start_stats_thread(self): # start a stats thread so we can periodically call update_payloads - global __RMAN_STATS_THREAD__ + global __RMAN_STATS_THREAD__ + if __RMAN_STATS_THREAD__: + __RMAN_STATS_THREAD__.join() + __RMAN_STATS_THREAD__ = None __RMAN_STATS_THREAD__ = threading.Thread(target=call_stats_update_payloads, args=(self, )) __RMAN_STATS_THREAD__.start() def reset(self): self.rman_license_failed = False self.rman_license_failed_message = '' + self.rman_is_xpu = False + self.rman_is_refining = False def start_render(self, depsgraph, for_background=False): @@ -450,15 +494,31 @@ def start_render(self, depsgraph, for_background=False): render_config = rman.Types.RtParamList() 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') 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 + try: bl_layer = depsgraph.view_layer self.rman_is_exporting = True self.rman_running = True - self.start_stats_thread() + self.start_export_stats_thread() self.rman_scene.export_for_final_render(depsgraph, self.sg_scene, bl_layer, is_external=is_external) self.rman_is_exporting = False + self.stats_mgr.reset_progress() self._dump_rib_(self.bl_scene.frame_current) rfb_log().info("Finished parsing scene. Total time: %s" % string_utils._format_time_(time.time() - time_start)) @@ -469,134 +529,111 @@ def start_render(self, depsgraph, for_background=False): self.del_bl_engine() return False - render_cmd = '' - if self.rman_render_into == 'it': + render_cmd = "prman" + if self.rman_render_into == 'blender': render_cmd = "prman -live" - elif self.rman_render_into == 'blender': - - if rendervariant == 'prman': - render_cmd = "prman -live" - else: - render_cmd = "prman -blocking" - else: - render_cmd = "prman -blocking" - render_cmd = self._append_render_cmd(render_cmd) 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) - if rendervariant != 'prman': - # FIXME: Remove this code path when we are able to get progress - # from XPU to determine when it reaches 100%. - # Also, make sure we render with prman -live in that case - dspy_dict = display_utils.get_dspy_dict(self.rman_scene) - filepath = dspy_dict['displays']['beauty']['filePath'] - - render = self.bl_scene.render - image_scale = 100.0 / render.resolution_percentage - result = self.bl_engine.begin_result(0, 0, - render.resolution_x * image_scale, - render.resolution_y * image_scale) - lay = result.layers[0] - try: - lay.load_from_file(filepath) - except: - pass - self.bl_engine.end_result(result) - self.stop_render() - - else: - 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() + 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') + # register any AOV's as passes + for i, dspy_nm in enumerate(dspy_dict['displays'].keys()): + if i == 0: + continue - 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)) + num_channels = -1 + while num_channels == -1: + num_channels = self.get_numchannels(i) - result = self.bl_engine.begin_result(0, 0, - size_x, - size_y, - view=render_view) + 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'] - 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) + 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: - render_pass = result.layers[0].passes.find_by_name(dspy_nm, render_view) - bl_image_rps[i] = render_pass - - 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 + 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.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 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: while self.bl_engine and not self.bl_engine.test_break() and self.rman_is_live_rendering: time.sleep(0.01) @@ -724,7 +761,7 @@ def start_bake_render(self, depsgraph, for_background=False): bl_layer = depsgraph.view_layer self.rman_is_exporting = True self.rman_running = True - self.start_stats_thread() + self.start_export_stats_thread() self.rman_scene.export_for_bake_render(depsgraph, self.sg_scene, bl_layer, is_external=is_external) self.rman_is_exporting = False @@ -733,6 +770,7 @@ def start_bake_render(self, depsgraph, for_background=False): render_cmd = "prman -blocking" render_cmd = self._append_render_cmd(render_cmd) self.sg_scene.Render(render_cmd) + self.start_stats_thread() except Exception as e: self.bl_engine.report({'ERROR'}, 'Export failed: %s' % str(e)) self.stop_render(stop_draw_thread=False) @@ -850,6 +888,8 @@ def start_interactive_render(self, context, depsgraph): rman.Dspy.DisableDspyServer() self.rman_callbacks.clear() ec = rman.EventCallbacks.Get() + ec.RegisterCallback("Render", live_render_cb, self) + self.rman_callbacks["Render"] = live_render_cb self.viewport_buckets.clear() self._draw_viewport_buckets = True else: @@ -870,6 +910,7 @@ def start_interactive_render(self, context, depsgraph): render_config = rman.Types.RtParamList() 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') self.sg_scene = self.sgmngr.CreateScene(config, render_config, self.stats_mgr.rman_stats_session) @@ -877,7 +918,7 @@ def start_interactive_render(self, context, depsgraph): self.rman_scene_sync.sg_scene = self.sg_scene rfb_log().info("Parsing scene...") self.rman_is_exporting = True - self.start_stats_thread() + self.start_export_stats_thread() self.rman_scene.export_for_interactive_render(context, depsgraph, self.sg_scene) self.rman_is_exporting = False @@ -887,15 +928,20 @@ def start_interactive_render(self, context, depsgraph): render_cmd = "prman -live" render_cmd = self._append_render_cmd(render_cmd) self.sg_scene.Render(render_cmd) + self.start_stats_thread() rfb_log().info("RenderMan Viewport Render Started.") if render_into_org != '': rm.render_ipr_into = render_into_org - # start a thread to periodically call engine.tag_redraw() + if not self.rman_is_xpu: + # for now, we only set the redraw callback for RIS + self.set_redraw_func() + # start a thread to periodically call engine.tag_redraw() __DRAW_THREAD__ = threading.Thread(target=draw_threading_func, args=(self, )) __DRAW_THREAD__.start() + return True except Exception as e: bpy.ops.renderman.printer('INVOKE_DEFAULT', level="ERROR", message='Export failed: %s' % str(e)) @@ -932,7 +978,7 @@ def start_swatch_render(self, depsgraph): if not self._check_prman_license(): return False self.rman_is_live_rendering = True - self.sg_scene.Render("prman -live") + self.sg_scene.Render("prman") render = self.rman_scene.bl_scene.render render_view = self.bl_engine.active_view_get() image_scale = render.resolution_percentage / 100.0 @@ -1082,6 +1128,19 @@ def get_blender_dspy_plugin(self): __BLENDER_DSPY_PLUGIN__ = ctypes.CDLL(os.path.join(envconfig().rmantree, 'lib', 'plugins', 'd_blender%s' % ext)) return __BLENDER_DSPY_PLUGIN__ + + def set_redraw_func(self): + # pass our callback function to the display driver + dspy_plugin = self.get_blender_dspy_plugin() + dspy_plugin.SetRedrawCallback(__CALLBACK_FUNC__) + + def has_buffer_updated(self): + dspy_plugin = self.get_blender_dspy_plugin() + return dspy_plugin.HasBufferUpdated() + + def reset_buffer_updated(self): + dspy_plugin = self.get_blender_dspy_plugin() + dspy_plugin.ResetBufferUpdated() def draw_pixels(self, width, height): self.viewport_res_x = width diff --git a/rman_scene.py b/rman_scene.py index 932574b0..5f26662d 100644 --- a/rman_scene.py +++ b/rman_scene.py @@ -1154,10 +1154,10 @@ def export_hider(self): options.SetInteger(self.rman.Tokens.Rix.k_hider_decidither, rm.hider_decidither) 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) - options.SetInteger(self.rman.Tokens.Rix.k_hider_incremental, 1) pv = rm.ipr_ri_pixelVariance - - if (not self.external_render and rm.render_into == 'blender') or rm.enable_checkpoint: + + # force incremental when checkpointing + if rm.enable_checkpoint: options.SetInteger(self.rman.Tokens.Rix.k_hider_incremental, 1) if not rm.sample_motion_blur: diff --git a/rman_scene_sync.py b/rman_scene_sync.py index f9ff4756..535edc15 100644 --- a/rman_scene_sync.py +++ b/rman_scene_sync.py @@ -258,10 +258,11 @@ def _obj_geometry_updated(self, obj): for k,v in rman_sg_node.instances.items(): self.rman_scene.attach_material(ob, v) - if not ob.show_instancer_for_viewport: - rman_sg_node.sg_node.SetHidden(1) - else: - rman_sg_node.sg_node.SetHidden(-1) + 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: diff --git a/rman_stats.py b/rman_stats.py index 105a407c..ab06d87c 100644 --- a/rman_stats.py +++ b/rman_stats.py @@ -278,6 +278,9 @@ def get_status(self): else: return 'Disconnected' + def reset_progress(self): + self._progress = 0 + def check_payload(self, jsonData, name): try: dat = jsonData[name] diff --git a/rman_translators/rman_mesh_translator.py b/rman_translators/rman_mesh_translator.py index ede9ec5e..ff647a3c 100644 --- a/rman_translators/rman_mesh_translator.py +++ b/rman_translators/rman_mesh_translator.py @@ -368,7 +368,9 @@ def update(self, ob, rman_sg_mesh, input_mesh=None, sg_node=None): if nverts == []: if not input_mesh: ob.to_mesh_clear() - sg_node = None + rman_sg_mesh.npoints = 0 + rman_sg_mesh.npolys = 0 + rman_sg_mesh.nverts = 0 rman_sg_mesh.is_transforming = False rman_sg_mesh.is_deforming = False return None diff --git a/rman_translators/rman_translator.py b/rman_translators/rman_translator.py index 27d46b32..a58a484d 100644 --- a/rman_translators/rman_translator.py +++ b/rman_translators/rman_translator.py @@ -110,6 +110,8 @@ def export_object_primvars(self, ob, rman_sg_node, sg_node=None): sg_node.SetPrimVars(primvars) def export_object_id(self, ob, rman_sg_node, ob_inst): + if not rman_sg_node.sg_node: + return name = ob.name_full attrs = rman_sg_node.sg_node.GetAttributes() rman_type = object_utils._detect_primitive_(ob) @@ -194,6 +196,8 @@ def export_light_linking_attributes(self, ob, attrs): attrs.SetString(self.rman_scene.rman.Tokens.Rix.k_lightfilter_subset, '') def export_object_attributes(self, ob, rman_sg_node): + if not rman_sg_node.sg_node: + return name = ob.name_full rm = ob.renderman diff --git a/rman_ui/rman_ui_light_handlers/__init__.py b/rman_ui/rman_ui_light_handlers/__init__.py index 29b3965e..e5fbce6d 100644 --- a/rman_ui/rman_ui_light_handlers/__init__.py +++ b/rman_ui/rman_ui_light_handlers/__init__.py @@ -9,6 +9,7 @@ from ...rfb_logger import rfb_log from ...rman_constants import RMAN_AREA_LIGHT_TYPES from .barn_light_filter_draw_helper import BarnLightFilterDrawHelper +from .frustrum_draw_helper import FrustumDrawHelper from mathutils import Vector, Matrix from bpy.app.handlers import persistent import mathutils @@ -16,6 +17,7 @@ import ice _DRAW_HANDLER_ = None +_FRUSTUM_DRAW_HELPER_ = None _BARN_LIGHT_DRAW_HELPER_ = None _PI0_5_ = 1.570796327 _PRMAN_TEX_CACHE_ = dict() @@ -507,12 +509,12 @@ _SELECTED_COLOR_ = bpy.context.preferences.themes['Default'].view_3d.object_active _WIRE_COLOR_ = bpy.context.preferences.themes['Default'].view_3d.wire -def set_selection_color(ob): +def set_selection_color(ob, opacity=1.0): global _SELECTED_COLOR_, _WIRE_COLOR_ if ob in bpy.context.selected_objects: - col = (_SELECTED_COLOR_[0], _SELECTED_COLOR_[1], _SELECTED_COLOR_[2], 1) + col = (_SELECTED_COLOR_[0], _SELECTED_COLOR_[1], _SELECTED_COLOR_[2], opacity) else: - col = (_WIRE_COLOR_[0], _WIRE_COLOR_[1], _WIRE_COLOR_[2], 1) + col = (_WIRE_COLOR_[0], _WIRE_COLOR_[1], _WIRE_COLOR_[2], opacity) _SHADER_.uniform_float("color", col) @@ -784,10 +786,13 @@ def draw_line_shape(ob, shader, pts, indices): if do_draw: batch = batch_for_shader(shader, 'LINES', {"pos": pts}, indices=indices) bgl.glEnable(bgl.GL_DEPTH_TEST) + bgl.glEnable(bgl.GL_BLEND) batch.draw(shader) bgl.glDisable(bgl.GL_DEPTH_TEST) + bgl.glDisable(bgl.GL_BLEND) def draw_rect_light(ob): + global _FRUSTUM_DRAW_HELPER_ _SHADER_.bind() set_selection_color(ob) @@ -812,10 +817,29 @@ def draw_rect_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') rm = ob.data.renderman light_shader = rm.get_light_node() - light_shader_name = rm.get_light_node_name() + light_shader_name = rm.get_light_node_name() + + m = ob_matrix + coneAngle = getattr(light_shader, 'coneAngle', 90.0) + if coneAngle < 90.0: + softness = getattr(light_shader, 'coneSoftness', 0.0) + depth = getattr(rm, 'rman_coneAngleDepth', 5.0) + opacity = getattr(rm, 'rman_coneAngleOpacity', 0.5) + set_selection_color(ob, opacity=opacity) + _FRUSTUM_DRAW_HELPER_.update_input_params(method='rect', + coneAngle=coneAngle, + coneSoftness=softness, + rman_coneAngleDepth=depth + ) + vtx_buffer = _FRUSTUM_DRAW_HELPER_.vtx_buffer() + + pts = [m @ Vector(pt) for pt in vtx_buffer ] + 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': @@ -826,9 +850,10 @@ def draw_rect_light(ob): 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) + draw_solid(ob, pts, m, uvs=uvs, tex=tex, col=col) def draw_sphere_light(ob): + global _FRUSTUM_DRAW_HELPER_ _SHADER_.bind() @@ -860,15 +885,34 @@ def draw_sphere_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.Scale(0.5, 4) @ Matrix.Rotation(math.radians(90.0), 4, 'X') - idx_buffer = make_sphere_idx_buffer() rm = ob.data.renderman light_shader = rm.get_light_node() - light_shader_name = rm.get_light_node_name() + light_shader_name = rm.get_light_node_name() + + m = ob_matrix + coneAngle = getattr(light_shader, 'coneAngle', 90.0) + if coneAngle < 90.0: + softness = getattr(light_shader, 'coneSoftness', 0.0) + depth = getattr(rm, 'rman_coneAngleDepth', 5.0) + opacity = getattr(rm, 'rman_coneAngleOpacity', 0.5) + set_selection_color(ob, opacity=opacity) + _FRUSTUM_DRAW_HELPER_.update_input_params(method='rect', + coneAngle=coneAngle, + coneSoftness=softness, + rman_coneAngleDepth=depth + ) + vtx_buffer = _FRUSTUM_DRAW_HELPER_.vtx_buffer() + + pts = [m @ Vector(pt) for pt in vtx_buffer ] + 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') + idx_buffer = make_sphere_idx_buffer() if light_shader_name in ['PxrSphereLight']: col = light_shader.lightColor sphere_indices = [(idx_buffer[i], idx_buffer[i+1], idx_buffer[i+2]) for i in range(0, len(idx_buffer)-2) ] - draw_solid(ob, make_sphere(), m, col=col, indices=sphere_indices) + draw_solid(ob, make_sphere(), m, col=col, indices=sphere_indices) def draw_envday_light(ob): @@ -954,6 +998,7 @@ def draw_envday_light(ob): draw_line_shape(ob, _SHADER_, sphere_shape, sphere_indices) def draw_disk_light(ob): + global _FRUSTUM_DRAW_HELPER_ _SHADER_.bind() @@ -979,13 +1024,31 @@ def draw_disk_light(ob): R_inside_indices = _get_indices(s_rmanLightLogo['R_inside']) draw_line_shape(ob, _SHADER_, R_inside, R_inside_indices) - ob_matrix = Matrix(ob.matrix_world) - m = ob_matrix @ Matrix.Rotation(math.radians(180.0), 4, 'Y') - + ob_matrix = Matrix(ob.matrix_world) rm = ob.data.renderman - light_shader = rm.get_light_node() + light_shader = rm.get_light_node() + + m = ob_matrix + coneAngle = getattr(light_shader, 'coneAngle', 90.0) + if coneAngle < 90.0: + softness = getattr(light_shader, 'coneSoftness', 0.0) + depth = getattr(rm, 'rman_coneAngleDepth', 5.0) + opacity = getattr(rm, 'rman_coneAngleOpacity', 0.5) + set_selection_color(ob, opacity=opacity) + _FRUSTUM_DRAW_HELPER_.update_input_params(method='rect', + coneAngle=coneAngle, + coneSoftness=softness, + rman_coneAngleDepth=depth + ) + vtx_buffer = _FRUSTUM_DRAW_HELPER_.vtx_buffer() + + pts = [m @ Vector(pt) for pt in vtx_buffer ] + 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') col = light_shader.lightColor - draw_solid(ob, s_diskLight, m, col=col) + draw_solid(ob, s_diskLight, m, col=col) def draw_dist_light(ob): @@ -1033,6 +1096,11 @@ 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') + 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 = m @ Matrix.Scale(0.5, 4) rays = [m @ Vector(pt) for pt in s_portalRays] @@ -1067,6 +1135,7 @@ def draw_dome_light(ob): draw_solid(ob, sphere_pts, m, uvs=make_sphere_uvs(), tex=tex, indices=sphere_indices) def draw_cylinder_light(ob): + global _FRUSTUM_DRAW_HELPER_ _SHADER_.bind() @@ -1078,8 +1147,27 @@ def draw_cylinder_light(ob): draw_line_shape(ob, _SHADER_, cylinder, s_cylinderLight['indices']) rm = ob.data.renderman - col = rm.get_light_node().lightColor - draw_solid(ob, s_cylinderLight['vtx'], m, col=col, indices=s_cylinderLight['indices_tris']) + light_shader = rm.get_light_node() + coneAngle = getattr(light_shader, 'coneAngle', 90.0) + if coneAngle < 90.0: + softness = getattr(light_shader, 'coneSoftness', 0.0) + depth = getattr(rm, 'rman_coneAngleDepth', 5.0) + opacity = getattr(rm, 'rman_coneAngleOpacity', 0.5) + set_selection_color(ob, opacity=opacity) + _FRUSTUM_DRAW_HELPER_.update_input_params(method='rect', + coneAngle=coneAngle, + coneSoftness=softness, + rman_coneAngleDepth=depth + ) + vtx_buffer = _FRUSTUM_DRAW_HELPER_.vtx_buffer() + + pts = [m @ Vector(pt) for pt in vtx_buffer ] + indices = _FRUSTUM_DRAW_HELPER_.idx_buffer(len(pts), 0, 0) + draw_line_shape(ob, _SHADER_, pts, indices) + + col = light_shader.lightColor + draw_solid(ob, s_cylinderLight['vtx'], m, col=col, indices=s_cylinderLight['indices_tris']) + def draw_arc(a, b, numSteps, quadrant, xOffset, yOffset, pts): stepAngle = float(_PI0_5_ / numSteps) @@ -1354,8 +1442,6 @@ def draw_barn_light_filter(ob): set_selection_color(ob) - if not _BARN_LIGHT_DRAW_HELPER_: - _BARN_LIGHT_DRAW_HELPER_ = BarnLightFilterDrawHelper() _BARN_LIGHT_DRAW_HELPER_.update_input_params(ob) vtx_buffer = _BARN_LIGHT_DRAW_HELPER_.vtx_buffer() @@ -1371,8 +1457,8 @@ def draw(): global _RMAN_TEXTURED_LIGHTS_ if bpy.context.engine != 'PRMAN_RENDER': - return - + return + scene = bpy.context.scene lights_set = set() user_map = bpy.data.user_map(subset=bpy.data.lights, value_types={'OBJECT'}) @@ -1478,6 +1564,14 @@ def clear_gl_tex_cache(bl_scene=None): def register(): global _DRAW_HANDLER_ + global _FRUSTUM_DRAW_HELPER_ + global _BARN_LIGHT_DRAW_HELPER_ + + if not _FRUSTUM_DRAW_HELPER_: + _FRUSTUM_DRAW_HELPER_ = FrustumDrawHelper() + + if not _BARN_LIGHT_DRAW_HELPER_: + _BARN_LIGHT_DRAW_HELPER_ = BarnLightFilterDrawHelper() _DRAW_HANDLER_ = bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW') def unregister(): 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 ffa59b6b..74a3bfe6 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 @@ -19,7 +19,7 @@ def _gl_lines(idx_buffer, start_vtx, num_vtx, start_idx, loop=False): """Fills the index buffer to draw a number of lines. Args: - - idx_buffer (MIndexBuffer): A pre-initialized index buffer to fill. May already contain valid data. + - idx_buffer (list): A pre-initialized index buffer to fill. May already contain valid data. - start_vtx (int): index of the primitive's first vertex in the vertex buffer. - num_vtx (int): number of vertices in the primitive - start_idx (p1_type): position of our first write in the index buffer. @@ -483,7 +483,6 @@ def idx_buffer(self, num_vtx, start_idx, inst_idx): Fill the provided index buffer to draw the shape. Args: - - idx_buffer (omr.MIndexBuffer): un-allocated storage for our result. - num_vtx (int): The total number of vertices in the VBO. - startIdx (int): the index of our first vtx in the VBO - item_idx (int): 0 = outer frustum, 1 = inner frustum, 2 = frustum edges diff --git a/rman_ui/rman_ui_light_handlers/frustrum_draw_helper.py b/rman_ui/rman_ui_light_handlers/frustrum_draw_helper.py new file mode 100644 index 00000000..0785ac0c --- /dev/null +++ b/rman_ui/rman_ui_light_handlers/frustrum_draw_helper.py @@ -0,0 +1,218 @@ +import math +from ...rfb_logger import rfb_log + +POS = [[-0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, -0.5, 0.0]] + +def _gl_lines(idx_buffer, vtxbuf_start_idx, num_vtx, start_idx, loop=False): + """Fills the index buffer to draw a number of lines. + + Args: + - idx_buffer (lise): A pre-initialized index buffer to fill. May already contain valid data. + - vtxbuf_start_idx (int): index of the primitive's first vertex in the vertex buffer. + - num_vtx (int): number of vertices in the primitive + - start_idx (p1_type): position of our first write in the index buffer. + + Kwargs: + - loop: add a line from the last vertex to the first one if True. + """ + # print (' _gl_lines(%s, vtxbuf_start_idx=%d, num_vtx=%d, start_idx=%d, loop=%s)' + # % (idx_buffer, vtxbuf_start_idx, num_vtx, start_idx, loop)) + num_indices = num_vtx * 2 + if not loop: + num_indices -= 2 + vtx_idx = vtxbuf_start_idx + last_idx = start_idx + num_indices - 2 + for i in range(start_idx, start_idx + num_indices, 2): + idx_buffer[i] = vtx_idx + if i == last_idx and loop: + idx_buffer[i + 1] = vtxbuf_start_idx + else: + idx_buffer[i + 1] = vtx_idx + 1 + vtx_idx += 1 + + +class FrustumDrawHelper(object): + + def __init__(self, *args, **kwargs): + self.depth = 5.0 + + def update_input_params(self, *args, **kwargs): + self.method = kwargs.get('method', 'rect') + self.base_shape = self._build_base_shape() + self.angle = kwargs.get('coneAngle', 90.0) + self.softness = kwargs.get('coneSoftness', 0.0) + self.depth = kwargs.get('rman_coneAngleDepth', 5.0) + + def disk_vtx_buffer(self): + vtxs = [] + subdivs = 32 + radius = 0.5 + pos = [0.0, 0.0, 0.0] + theta_step = 2.0 * math.pi / float(subdivs) + + # default with z axis + idx1 = 0 + idx2 = 1 + + # compute + for i in range(subdivs): + theta = float(i) * theta_step + p = [pos[0], pos[1], pos[2]] + p[idx1] = radius * math.cos(theta) + pos[idx1] + p[idx2] = radius * math.sin(theta) + pos[idx2] + vtxs.append(p) + # vtxs.append(vtxs[0]) + + return vtxs + + def rect_vtx_buffer(self): + """Return a list of vertices (list) in local space.""" + vtxs = [] + for i in range(len(POS)): + vtx = [POS[i][0], + POS[i][1], + POS[i][2]] + + vtxs.append(vtx) + + return vtxs + + def _build_base_shape(self): + if self.method == 'rect': + return self.rect_vtx_buffer() + elif self.method == 'disk': + return self.disk_vtx_buffer() + else: + raise RuntimeError("Unknown base shape method.") + + def vtx_buffer_count(self): + """Return the number of vertices in this buffer.""" + return len(self.base_shape) * 3 + 4 + + def vtx_buffer(self): + """Return a list of vertices (list) in local space. + + Use the vtx_list (the original light shape) to build the outer coneAngle + at the specified depth. + """ + + if self.angle >= 90.0: + # do not draw + return [[0.0, 0.0, 0.0]] * self.vtx_buffer_count() + + # print 'frustum.vtx_buffer()' + # print ' |__ base shape' + # for v in self.base_shape: + # print ' |__ %s' % v + + # far shape + # print ' |__ far angle' + vertices = [] + rad = math.radians(self.angle) + d = 1.0 + max(0.0, self.depth) + rscale = 1.0 + math.tan(rad) * 2.0 * d + for vtx in self.base_shape: + x = vtx[0] * rscale + y = vtx[1] * rscale + z = vtx[2] - d + vertices.append([x, y, z]) + # print ' |__ #%02d: %0.2f %0.2f %0.2f' % (len(vertices)-1, + # x, y, z) + + # far softness + # print ' |__ far softness' + soft_scale = 1.0 - self.softness + for vtx in self.base_shape: + x = vtx[0] * rscale * soft_scale + y = vtx[1] * rscale * soft_scale + z = vtx[2] - d + vertices.append([x, y, z]) + # print ' |__ #%02d: %0.2f %0.2f %0.2f' % (len(vertices)-1, + # x, y, z) + + # near softness + # print ' |__ near softness' + for vtx in self.base_shape: + vertices.append([vtx[0] * soft_scale, + vtx[1] * soft_scale, + vtx[2]]) + # print ' |__ #%02d: %0.2f %0.2f %0.2f' % (len(vertices)-1, + # vertices[-1][0], + # vertices[-1][1], + # vertices[-1][2]) + + # frustum edges + # print ' |__ edges' + num_vtx = len(self.base_shape) + vtx_step = num_vtx // 4 + for i in range(0, num_vtx, vtx_step): + vertices.append(self.base_shape[i]) + # print ' |__ #%02d: %0.2f %0.2f %0.2f' % (len(vertices)-1, + # vertices[-1][0], + # vertices[-1][1], + # vertices[-1][2]) + + return vertices + + def idx_buffer(self, num_vtx, start_idx, inst_idx): + """ + Fill the provided index buffer to draw the shape. + + Args: + - num_vtx (int): The total number of vertices in the VBO. + - startIdx (int): the index of our first vtx in the VBO + - item_idx (int): 0 = outer frustum, 1 = inner frustum, 2 = frustum edges + """ + # print 'idx_buffer: %s' % self.__class__ + # print('>> frustum.idx_buffer(%s, %d, %d, %d)' % + # (idx_buffer, num_vtx, start_idx, inst_idx)) + + # 3 shapes in the frustum with same number of vtxs. Plus 4 edges. + grp_n_vtx = (num_vtx - 4) // 3 + + num_indices_per_shape = [grp_n_vtx * 2, + grp_n_vtx * 2 * 2, + 4 * 2] + + n_indices = num_indices_per_shape[0] + \ + num_indices_per_shape[1] + \ + num_indices_per_shape[2] + # print ' |__ generating %s indices' % n_indices + indices = list([None] * n_indices) + + for item_idx in range(3): + + if item_idx == 0: + # angle: far shape + _gl_lines(indices, start_idx, grp_n_vtx, 0, loop=True) + + elif item_idx == 1: + # softness: far shape + _gl_lines(indices, start_idx + grp_n_vtx, + grp_n_vtx, grp_n_vtx * 2, loop=True) + # softness: near shape + _gl_lines(indices, start_idx + grp_n_vtx *2, + grp_n_vtx, grp_n_vtx * 4, loop=True) + elif item_idx == 2: + # edges + in_idx = grp_n_vtx * 3 * 2 + # frustum edges + # we need to create 4 equi-distant lines from the far angle + # shape to the base shape. + stride = grp_n_vtx // 4 + ofst = (grp_n_vtx * 3) + near_vtx_idx = start_idx + ofst + far_vtx_idx = start_idx + # print ' |__ frustum' + for i in range(in_idx, in_idx + num_indices_per_shape[2], 2): + indices[i] = near_vtx_idx + indices[i + 1] = far_vtx_idx + # print ' |__ %d -> %d' % (indices[i], indices[1+1]) + near_vtx_idx += 1 + far_vtx_idx += stride + + else: + print('WARNING: unknown item_idx: %s' % item_idx) + + indices = [indices[i:i+2] for i in range(0, len(indices), 2)] + return indices \ No newline at end of file diff --git a/rman_ui/rman_ui_material_panels.py b/rman_ui/rman_ui_material_panels.py index b7f91a87..91c221ff 100644 --- a/rman_ui/rman_ui_material_panels.py +++ b/rman_ui/rman_ui_material_panels.py @@ -454,8 +454,7 @@ def draw(self, context): elif mat: split.template_ID(space, "pin_id") split.separator() - - + class DATA_PT_renderman_node_shader_light(ShaderNodePanel, Panel): bl_label = "Light Parameters" bl_context = 'data' @@ -601,6 +600,26 @@ def draw(self, context): layout.prop(rm, 'dome_light_portal') +class DATA_PT_renderman_node_shader_light_viewport(ShaderNodePanel, Panel): + bl_label = "Viewport" + bl_context = 'data' + + @classmethod + def poll(cls, context): + rd = context.scene.render + return rd.engine == 'PRMAN_RENDER' and hasattr(context, "light") \ + and context.light is not None and hasattr(context.light, 'renderman') \ + and context.light.renderman.renderman_light_role != 'RMAN_LIGHTFILTER' \ + and context.light.renderman.get_light_node_name() in ['PxrRectLight', 'PxrCylinderLight', 'PxrSphereLight', 'PxrDiskLight'] + + def draw(self, context): + layout = self.layout + light = context.light + node = light.renderman.get_light_node() + if getattr(node, 'coneAngle', 90.0) >= 90.0: + layout.enabled = False + layout.prop(light.renderman, 'rman_coneAngleDepth') + layout.prop(light.renderman, 'rman_coneAngleOpacity') class MATERIAL_PT_renderman_shader_light_filters(RENDERMAN_UL_LightFilters, Panel): bl_context = "material" bl_label = "Light Filters" @@ -628,6 +647,7 @@ def draw(self, context): DATA_PT_renderman_node_filters_light, DATA_PT_renderman_node_portal_light, DATA_PT_renderman_node_dome_light, + DATA_PT_renderman_node_shader_light_viewport, PRMAN_PT_context_material, MATERIAL_PT_renderman_shader_light_filters ] diff --git a/rman_ui/rman_ui_viewport.py b/rman_ui/rman_ui_viewport.py index 9467fb4d..859e46e4 100644 --- a/rman_ui/rman_ui_viewport.py +++ b/rman_ui/rman_ui_viewport.py @@ -31,6 +31,9 @@ def poll(cls, context): def draw(self, context): layout = self.layout + op = layout.operator('renderman_viewport.change_integrator', text='Reset') + op.viewport_integrator = 'RESET' + layout.separator() for node in rman_bl_nodes.__RMAN_INTEGRATOR_NODES__: if node.name not in __HIDDEN_INTEGRATORS__: layout.operator_context = 'EXEC_DEFAULT' @@ -107,10 +110,19 @@ class PRMAN_OT_Viewport_Integrators(bpy.types.Operator): description="Viewport integrator" ) + @classmethod + def description(cls, context, properties): + help = cls.bl_description + if properties.viewport_integrator == 'RESET': + help = 'Reset back to the scene integrator' + return help + def execute(self, context): rman_render = RmanRender.get_rman_render() - rman_render.rman_scene_sync.update_viewport_integrator(context, self.viewport_integrator) - + if self.viewport_integrator == 'RESET': + rman_render.rman_scene_sync.update_integrator(context) + else: + rman_render.rman_scene_sync.update_viewport_integrator(context, self.viewport_integrator) return {"FINISHED"} class PRMAN_OT_Viewport_Refinement(bpy.types.Operator):