def wrangle_medium(medium): """Output a NamedMedium from the input oppath""" if not medium: return None if medium in scene_state.medium_nodes: return None scene_state.medium_nodes.add(medium) medium_vop = BaseNode.from_node(medium) if medium_vop is None: return None if medium_vop.directive != "medium": return None with api.AttributeBlock(): coord_sys = medium_vop.coord_sys if coord_sys: api.Transform(coord_sys) colorspace = medium_vop.colorspace if colorspace: api.ColorSpace(colorspace) api.MakeNamedMedium( medium_vop.path, medium_vop.directive_type, medium_vop.paramset ) return medium_vop.path
def medium_prim_paramset(prim, paramset=None): """Build a ParamSet of medium values based off of hou.Prim attribs""" medium_paramset = ParamSet(paramset) # NOTE: # Testing for prim attribs on each prim is a bit redundat but # in general its not an issue as you won't have huge numbers of # volumes. If this does become an issue, attribs can be stored in # a dict and searched from there. (This includes evaluating the # pbrt_interior node. # Initialize with the interior shader on the prim, if it exists. try: interior = prim.stringAttribValue("pbrt_interior") interior = BaseNode.from_node(interior) except hou.OperationFailed: interior = None if interior and interior.directive_type == "pbrt_medium": medium_paramset |= interior.paramset try: preset_value = prim.stringAttribValue("preset") if preset_value: medium_paramset.replace(PBRTParam("string", "preset", preset_value)) except hou.OperationFailed: pass try: g_value = prim.floatAttribValue("g") medium_paramset.replace(PBRTParam("float", "g", g_value)) except hou.OperationFailed: pass try: scale_value = prim.floatAttribValue("scale") medium_paramset.replace(PBRTParam("float", "scale", scale_value)) except hou.OperationFailed: pass try: sigma_a_value = prim.floatListAttribValue("sigma_a") if len(sigma_a_value) == 3: medium_paramset.replace(PBRTParam("rgb", "sigma_a", sigma_a_value)) except hou.OperationFailed: pass try: sigma_s_value = prim.floatListAttribValue("sigma_s") if len(sigma_s_value) == 3: medium_paramset.replace(PBRTParam("rgb", "sigma_s", sigma_s_value)) except hou.OperationFailed: pass return medium_paramset
def wrangle_node_parm(obj, parm_name, now): parm_selection = {parm_name: SohoPBRT(parm_name, "string", [""], False)} parms = obj.evaluate(parm_selection, now) if not parms: return None node_path = parms[parm_name].Value[0] if not node_path: return None return BaseNode(node_path)
def wrangle_preworld_medium(obj, wrangler, now): """Output a NamedMedium from the input oppath""" # Due to PBRT's scene description we can't use the standard wrangle_medium # when declaring a medium and its transform/colorspace when attached to a # camera. This is because we don't have AttributeBegin/End blocks to pop # the stack, so when we declare the transform for the medium we need to so # with respect to being in the camera's coordinate system. We'll extract # the translates from the camera, to establish a pivot. medium = obj.wrangleString(wrangler, "pbrt_exterior", now, [None])[0] if not medium: return None if medium in scene_state.medium_nodes: return None scene_state.medium_nodes.add(medium) medium_vop = BaseNode.from_node(medium) if medium_vop is None: return None if medium_vop.directive != "medium": return None coord_sys = medium_vop.coord_sys if coord_sys: cam_xform = hou.Matrix4( get_transform(obj, now, invert=False, flipz=False)) cam_pivot = cam_xform.extractTranslates() cam_pivot = hou.hmath.buildTranslate(cam_pivot).inverted() xform = hou.Matrix4(coord_sys) xform *= cam_pivot api.Transform(xform.asTuple()) colorspace = medium_vop.colorspace if colorspace: api.ColorSpace(colorspace) api.MakeNamedMedium(medium_vop.path, medium_vop.directive_type, medium_vop.paramset) api.Identity() # Restore Colorspace if one was set on the Medium if colorspace: scene_cs = [] if obj.evalString("pbrt_colorspace", now, scene_cs): scene_cs = scene_cs[0] else: scene_cs = "srgb" if scene_cs != colorspace: api.ColorSpace(scene_cs) return medium_vop.path
def process_full_pt_instance_medium(instance_info, medium_type): gdp = instance_info.gdp if medium_type not in ("interior", "exterior"): return None, None medium_attrib_h = gdp.attribute("geo:point", "pbrt_" + medium_type) if medium_attrib_h < 0: return None, None medium = gdp.value(medium_attrib_h, instance_info.number)[0] # an empty string is valid here as it means no medium if medium == "": return medium, ParamSet() suffix = ":%s[%i]" % (instance_info.source, instance_info.number) medium_node = BaseNode.from_node(medium) medium_node.path_suffix = suffix if not (medium_node and medium_node.directive_type == "pbrt_medium"): return None, None medium_paramset = ParamSet(medium_node.paramset) # TODO: Currently both this and the geometry overrides # for mediums only support "rgb" and not spectrums. parms = { "sigma_a": "rgb", "sigma_s": "rgb", "preset": "string", "g": "float", "scale": "float", } for parm, ptype in parms.iteritems(): attrib_name = "%s_%s" % (medium_type, parm) attrib_h = gdp.attribute("geo:point", attrib_name) if attrib_h < 0: continue # TODO: Checks? # attrib_size = gdp.attribProperty(attrib_h, "geo:vectorsize")[0] # attrib_storage = gdp.attribProperty(attrib_h, "geo:storage")[0] val = gdp.value(attrib_h, instance_info.number) medium_paramset.replace(PBRTParam(ptype, parm, val)) # We might be outputing a named medium even if its not going to be needed # as in the case of instancing volume prims api.MakeNamedMedium(medium_node.full_name, "homogeneous", medium_paramset) return medium_node.full_name, medium_paramset
def process_full_pt_instance_medium(instance_info, medium_type): gdp = instance_info.gdp if medium_type not in ("interior", "exterior"): return None, None medium_attrib_h = gdp.attribute("geo:point", "pbrt_" + medium_type) if medium_attrib_h < 0: return None, None medium = gdp.value(medium_attrib_h, instance_info.number)[0] # an empty string is valid here as it means no medium if medium == "": return medium, ParamSet() suffix = ":%s:%i" % (instance_info.source, instance_info.number) medium_node = BaseNode.from_node(medium) medium_node.path_suffix = suffix if not (medium_node and medium_node.directive == "medium"): return None, None medium_paramset = ParamSet(medium_node.paramset) # Look for attributes to create an override dictionary pt_attribs = gdp.globalValue("geo:pointattribs") overrides = {} for attrib in pt_attribs: if not attrib.startswith("{}_".format(medium_type)): continue if attrib in ("pbrt_interior", "pbrt_exterior"): continue parm_name = attrib.split("_", 1)[1] attrib_h = gdp.attribute("geo:point", attrib) if attrib_h < 0: continue val = gdp.value(attrib_h, instance_info.number) overrides[parm_name] = val if overrides: medium_paramset.update(medium_node.override_paramset(overrides)) # We might be outputing a named medium even if its not going to be needed # as in the case of instancing volume prims api.MakeNamedMedium( medium_node.full_name, medium_node.directive_type, medium_paramset ) return medium_node.full_name, medium_paramset
def output_medium(medium): """Output a NamedMedium from the input oppath""" if not medium: return None if medium in scene_state.medium_nodes: return None scene_state.medium_nodes.add(medium) medium_vop = BaseNode.from_node(medium) if medium_vop is None: return None if medium_vop.directive_type != "pbrt_medium": return None api.MakeNamedMedium(medium_vop.path, "homogeneous", medium_vop.paramset) return medium_vop.path
def wrangle_shading_network(node_path, name_prefix='', saved_nodes=None): # Depth first, as textures/materials need to be # defined before they are referenced # Use this to track if a node has been output or not. # if the saved_nodes is None, we use the global scene_state # otherwise we use the one passed in. This is useful for outputing # named materials within a nested Attribute Block. if saved_nodes is None: saved_nodes = scene_state.shading_nodes prefix_node_path = name_prefix + node_path if prefix_node_path in saved_nodes: return saved_nodes.add(prefix_node_path) hnode = hou.node(node_path) # Material or Texture? node = BaseNode.from_node(hnode) if node is None: return if node.directive == 'material': api_call = api.MakeNamedMaterial elif node.directive == 'texture': api_call = api.Texture else: return for node_input in node.inputs(): wrangle_shading_network(node_input, name_prefix=name_prefix, saved_nodes=saved_nodes) coord_sys = node.coord_sys if coord_sys: api.TransformBegin() api.Transform(coord_sys) api_call(name_prefix + node.name, node.output_type, node.directive_type, node.paramset) if coord_sys: api.TransformEnd() if api_call == api.MakeNamedMaterial: print() return
def wrangle_geo(obj, wrangler, now): parm_selection = { 'object:soppath' : SohoPBRT('object:soppath', 'string', [''], skipdefault=False), # NOTE: In order for shop_materialpath to evaluate correctly when using (full) instancing # shop_materialpath needs to be a 'shaderhandle' and not a 'string' # TODO: However this does not seem to apply to shop_materialpaths on the instance points. 'shop_materialpath' : SohoPBRT('shop_materialpath', 'shaderhandle', skipdefault=False), 'pbrt_rendersubd' : SohoPBRT('pbrt_rendersubd', 'bool', [False], False), 'pbrt_subdlevels' : SohoPBRT('pbrt_subdlevels', 'integer', [3], False, key='levels'), 'pbrt_computeN' : SohoPBRT('pbrt_computeN', 'bool', [True], False), # The combination of None as a default as well as ignore defaults being False is # important. 'None' implying the parm is missing and not available, and '' meaning # a vacuum medium. # We can't ignore defaults since a default might be the only way to set a medium # back to a vacuum. 'pbrt_interior' : SohoPBRT('pbrt_interior', 'string', [None], False), 'pbrt_exterior' : SohoPBRT('pbrt_exterior', 'string', [None], False), 'pbrt_ignorevolumes' : SohoPBRT('pbrt_ignorevolumes', 'bool', [False], True), 'pbrt_ignorematerials' : SohoPBRT('pbrt_ignorematerials', 'bool', [False], True), 'pbrt_splitdepth' : SohoPBRT('pbrt_splitdepth', 'integer', [3], True, key='splitdepth'), # We don't use the key=type since its a bit too generic of a name 'pbrt_curvetype' : SohoPBRT('pbrt_curvetype', 'string', ['flat'], True), 'pbrt_include' : SohoPBRT('pbrt_include', 'string', [''], False), 'pbrt_alpha_texture' : SohoPBRT('pbrt_alpha_texture', 'string', [''], skipdefault=False, key='alpha'), 'pbrt_shadowalpha_texture' : SohoPBRT('pbrt_shadowalpha_texture', 'string', [''], skipdefault=False, key='shadowalpha'), # TODO, Tesselation options? } properties = obj.evaluate(parm_selection, now) if 'shop_materialpath' not in properties: shop = '' else: shop = properties['shop_materialpath'].Value[0] if shop and shop in scene_state.shading_nodes: api.NamedMaterial(shop) interior = None exterior = None if 'pbrt_interior' in properties: interior = properties['pbrt_interior'].Value[0] if 'pbrt_exterior' in properties: exterior = properties['pbrt_exterior'].Value[0] # We only output a MediumInterface if one or both of the parms exist if interior is not None or exterior is not None: interior = '' if interior is None else interior exterior = '' if exterior is None else exterior api.MediumInterface(interior, exterior) for prop in ('alpha', 'shadowalpha'): alpha_tex = properties[prop].Value[0] alpha_node = BaseNode.from_node(alpha_tex) if ( alpha_node and alpha_node.directive == 'texture' and alpha_node.output_type == 'float'): if alpha_node.path not in scene_state.shading_nodes: wrangle_shading_network(alpha_node.path, saved_nodes=set()) else: # If the passed in alpha_texture wasn't valid, clear it so we don't add # it to the geometry if alpha_tex: api.Comment('%s is an invalid float texture' % alpha_tex) properties[prop].Value[0] = '' if properties['pbrt_include'].Value[0]: # If we have included a file, skip output any geo. api.Include(properties['pbrt_include'].Value[0]) return soppath = properties['object:soppath'].Value[0] if not soppath: api.Comment('Can not find soppath for object') return Geo.output_geo(soppath, now, properties) return
def wrangle_geo(obj, wrangler, now): parm_selection = { "object:soppath": SohoPBRT("object:soppath", "string", [""], skipdefault=False), "ptinstance": SohoPBRT("ptinstance", "integer", [0], skipdefault=False), # NOTE: In order for shop_materialpath to evaluate correctly when using # (full) instancing shop_materialpath needs to be a 'shaderhandle' # and not a 'string' # NOTE: However this does not seem to apply to shop_materialpaths on the # instance points and has to be done manually "shop_materialpath": SohoPBRT( "shop_materialpath", "shaderhandle", skipdefault=False ), "pbrt_rendersubd": SohoPBRT("pbrt_rendersubd", "bool", [False], False), "pbrt_subdlevels": SohoPBRT( "pbrt_subdlevels", "integer", [3], False, key="levels" ), "pbrt_computeN": SohoPBRT("pbrt_computeN", "bool", [True], False), "pbrt_reverseorientation": SohoPBRT( "pbrt_reverseorientation", "bool", [False], True ), # The combination of None as a default as well as ignore defaults being False # is important. 'None' implying the parm is missing and not available, # and '' meaning a vacuum medium. # We can't ignore defaults since a default might be the only way to set a # medium back to a vacuum. "pbrt_interior": SohoPBRT("pbrt_interior", "string", [None], False), "pbrt_exterior": SohoPBRT("pbrt_exterior", "string", [None], False), "pbrt_ignorevolumes": SohoPBRT("pbrt_ignorevolumes", "bool", [False], True), "pbrt_ignorematerials": SohoPBRT("pbrt_ignorematerials", "bool", [False], True), "pbrt_splitdepth": SohoPBRT( "pbrt_splitdepth", "integer", [3], True, key="splitdepth" ), # We don't use the key=type since its a bit too generic of a name "pbrt_curvetype": SohoPBRT("pbrt_curvetype", "string", ["flat"], True), "pbrt_include": SohoPBRT("pbrt_include", "string", [""], False), "pbrt_alpha_texture": SohoPBRT( "pbrt_alpha_texture", "string", [""], skipdefault=False, key="alpha" ), "pbrt_shadowalpha_texture": SohoPBRT( "pbrt_shadowalpha_texture", "string", [""], skipdefault=False, key="shadowalpha", ), # TODO, Tesselation options? } properties = obj.evaluate(parm_selection, now) if "shop_materialpath" not in properties: shop = "" else: shop = properties["shop_materialpath"].Value[0] # NOTE: Having to track down shop_materialpaths does not seem to be a requirement # with Mantra or RenderMan. Either its because I'm missing some # logic/initialization either in Soho or in the Shading HDAs. Or there is # some hardcoding in the Houdini libs that know how to translate # shop_materialpath point aassignments to shaders directly through a # SohoParm. Until that is figured out, we'll have to do it manually. interior = None exterior = None if "pbrt_interior" in properties: interior = properties["pbrt_interior"].Value[0] if "pbrt_exterior" in properties: exterior = properties["pbrt_exterior"].Value[0] if "pbrt_reverseorientation" in properties: if properties["pbrt_reverseorientation"].Value[0]: api.ReverseOrientation() pt_shop_found = False if properties["ptinstance"].Value[0] == 1: instance_info = Instancing.get_full_instance_info(obj, now) properties[".instance_info"] = instance_info if instance_info is not None: pt_shop_found = process_full_pt_instance_material(instance_info) interior, interior_paramset = process_full_pt_instance_medium( instance_info, "interior" ) exterior, exterior_paramset = process_full_pt_instance_medium( instance_info, "exterior" ) if interior_paramset is not None: properties[".interior_overrides"] = interior_paramset # If we found a point shop, don't output the default one here. if shop in scene_state.shading_nodes and not pt_shop_found: api.NamedMaterial(shop) # We only output a MediumInterface if one or both of the parms exist if interior is not None or exterior is not None: interior = "" if interior is None else interior exterior = "" if exterior is None else exterior api.MediumInterface(interior, exterior) for prop in ("alpha", "shadowalpha"): alpha_tex = properties[prop].Value[0] alpha_node = BaseNode.from_node(alpha_tex) if ( alpha_node and alpha_node.directive == "texture" and alpha_node.output_type == "float" ): if alpha_node.path not in scene_state.shading_nodes: wrangle_shading_network(alpha_node.path, saved_nodes=set()) else: # If the passed in alpha_texture wasn't valid, clear it so we don't add # it to the geometry if alpha_tex: api.Comment("%s is an invalid float texture" % alpha_tex) properties[prop].Value[0] = "" if properties["pbrt_include"].Value[0]: # If we have included a file, skip output any geo. api.Include(properties["pbrt_include"].Value[0]) return soppath = properties["object:soppath"].Value[0] if not soppath: api.Comment("Can not find soppath for object") return Geo.output_geo(soppath, now, properties) return
def wrangle_shading_network( node_path, name_prefix="", name_suffix="", use_named=True, saved_nodes=None, overrides=None, root=True, ): if node_path in scene_state.invalid_shading_nodes: return # Depth first, as textures/materials need to be # defined before they are referenced # Use this to track if a node has been output or not. # if the saved_nodes is None, we use the global scene_state # otherwise we use the one passed in. This is useful for outputing # named materials within a nested Attribute Block. if saved_nodes is None: saved_nodes = scene_state.shading_nodes # NOTE: We prefix and suffix names here so that there are not collisions when # using full point instancing. There is some possible redundancy as the same # network maybe recreated multiple times under different names if the # overrides are the same. A possible optimization for export and PBRT is to # do a prepass and build the networks before and keep a map to the pre-built # networks. For now we'll brute force it. presufed_node_path = name_prefix + node_path + name_suffix if presufed_node_path in saved_nodes: return hnode = hou.node(node_path) # Material or Texture? node = BaseNode.from_node(hnode) if node is None: api.Comment("Skipping %s since its not a Material or Texture node" % node_path) scene_state.invalid_shading_nodes.add(node_path) return else: saved_nodes.add(presufed_node_path) node.path_suffix = name_suffix node.path_prefix = name_prefix if node.directive == "material": api_call = api.MakeNamedMaterial if use_named else api.Material elif node.directive == "texture": api_call = api.Texture else: return paramset = node.paramset_with_overrides(overrides) for node_input in node.inputs(): wrangle_shading_network( node_input, name_prefix=name_prefix, name_suffix=name_suffix, use_named=use_named, saved_nodes=saved_nodes, overrides=overrides, root=False, ) coord_sys = node.coord_sys if coord_sys: api.TransformBegin() api.Transform(coord_sys) if api_call == api.Material: api_call(node.directive_type, paramset) else: api_call(node.full_name, node.output_type, node.directive_type, paramset) if coord_sys: api.TransformEnd() if api_call == api.MakeNamedMaterial: print() return
def wrangle_geo(obj, wrangler, now): parm_selection = { 'object:soppath': SohoPBRT('object:soppath', 'string', [''], skipdefault=False), 'ptinstance': SohoPBRT('ptinstance', 'integer', [0], skipdefault=False), # NOTE: In order for shop_materialpath to evaluate correctly when using (full) instancing # shop_materialpath needs to be a 'shaderhandle' and not a 'string' # NOTE: However this does not seem to apply to shop_materialpaths on the instance points # and has to be done manually 'shop_materialpath': SohoPBRT('shop_materialpath', 'shaderhandle', skipdefault=False), 'pbrt_rendersubd': SohoPBRT('pbrt_rendersubd', 'bool', [False], False), 'pbrt_subdlevels': SohoPBRT('pbrt_subdlevels', 'integer', [3], False, key='levels'), 'pbrt_computeN': SohoPBRT('pbrt_computeN', 'bool', [True], False), # The combination of None as a default as well as ignore defaults being False is # important. 'None' implying the parm is missing and not available, and '' meaning # a vacuum medium. # We can't ignore defaults since a default might be the only way to set a medium # back to a vacuum. 'pbrt_interior': SohoPBRT('pbrt_interior', 'string', [None], False), 'pbrt_exterior': SohoPBRT('pbrt_exterior', 'string', [None], False), 'pbrt_ignorevolumes': SohoPBRT('pbrt_ignorevolumes', 'bool', [False], True), 'pbrt_ignorematerials': SohoPBRT('pbrt_ignorematerials', 'bool', [False], True), 'pbrt_splitdepth': SohoPBRT('pbrt_splitdepth', 'integer', [3], True, key='splitdepth'), # We don't use the key=type since its a bit too generic of a name 'pbrt_curvetype': SohoPBRT('pbrt_curvetype', 'string', ['flat'], True), 'pbrt_include': SohoPBRT('pbrt_include', 'string', [''], False), 'pbrt_alpha_texture': SohoPBRT('pbrt_alpha_texture', 'string', [''], skipdefault=False, key='alpha'), 'pbrt_shadowalpha_texture': SohoPBRT('pbrt_shadowalpha_texture', 'string', [''], skipdefault=False, key='shadowalpha'), # TODO, Tesselation options? } properties = obj.evaluate(parm_selection, now) if 'shop_materialpath' not in properties: shop = '' else: shop = properties['shop_materialpath'].Value[0] # NOTE: Having to track down shop_materialpaths does not seem to be a requirement # with Mantra or RenderMan. Either its because I'm missing some logic/initialization # either in Soho or in the Shading HDAs. Or there is some hardcoding in the Houdini libs # that know how to translate shop_materialpath point aassignments to shaders directly # through a SohoParm. Until that is figured out, we'll have to do it manually. pt_shop_found = False if properties['ptinstance'].Value[0] == 1: pt_shop_found = Instancing.process_full_pt_instance_material(obj, now) # If we found a point shop, don't output the default one here. if shop in scene_state.shading_nodes and not pt_shop_found: api.NamedMaterial(shop) interior = None exterior = None if 'pbrt_interior' in properties: interior = properties['pbrt_interior'].Value[0] if 'pbrt_exterior' in properties: exterior = properties['pbrt_exterior'].Value[0] # We only output a MediumInterface if one or both of the parms exist if interior is not None or exterior is not None: interior = '' if interior is None else interior exterior = '' if exterior is None else exterior api.MediumInterface(interior, exterior) for prop in ('alpha', 'shadowalpha'): alpha_tex = properties[prop].Value[0] alpha_node = BaseNode.from_node(alpha_tex) if (alpha_node and alpha_node.directive == 'texture' and alpha_node.output_type == 'float'): if alpha_node.path not in scene_state.shading_nodes: wrangle_shading_network(alpha_node.path, saved_nodes=set()) else: # If the passed in alpha_texture wasn't valid, clear it so we don't add # it to the geometry if alpha_tex: api.Comment('%s is an invalid float texture' % alpha_tex) properties[prop].Value[0] = '' if properties['pbrt_include'].Value[0]: # If we have included a file, skip output any geo. api.Include(properties['pbrt_include'].Value[0]) return soppath = properties['object:soppath'].Value[0] if not soppath: api.Comment('Can not find soppath for object') return Geo.output_geo(soppath, now, properties) return
def wrangle_shading_network(node_path, name_prefix='', name_suffix='', use_named=True, saved_nodes=None, overrides=None): # Depth first, as textures/materials need to be # defined before they are referenced # Use this to track if a node has been output or not. # if the saved_nodes is None, we use the global scene_state # otherwise we use the one passed in. This is useful for outputing # named materials within a nested Attribute Block. if saved_nodes is None: saved_nodes = scene_state.shading_nodes # TODO: Currently (AFAICT) there isn't a case where prefixed and suffixed # names are required. The original intent was to handling instancing # variations, but given that fast instancing doesn't support material # variations it has become moot. The workaround for full instancing is # just to regenerate the shading network each time. # Partitioning based on the different shading overrides might still make # this useful but the current implementation doesn't need this. presufed_node_path = name_prefix + node_path + name_suffix if presufed_node_path in saved_nodes: return saved_nodes.add(presufed_node_path) hnode = hou.node(node_path) # Material or Texture? node = BaseNode.from_node(hnode) if node is None: return if node.directive == 'material': api_call = api.MakeNamedMaterial if use_named else api.Material elif node.directive == 'texture': api_call = api.Texture else: return for node_input in node.inputs(): wrangle_shading_network(node_input, name_prefix=name_prefix, name_suffix=name_suffix, use_named=use_named, saved_nodes=saved_nodes) coord_sys = node.coord_sys if coord_sys: api.TransformBegin() api.Transform(coord_sys) if api_call == api.Material: api_call(node.directive_type, node.paramset) else: api_call(presufed_node_path, node.output_type, node.directive_type, node.paramset) if coord_sys: api.TransformEnd() if api_call == api.MakeNamedMaterial: print() return
def smoke_prim_wrangler(prims, paramset=None, properties=None, override_node=None): """Outputs a "heterogeneous" Medium and bounding Shape for the input geometry The following attributes are checked for via medium_prim_paramset() - (See pbrt_medium node for what each parm does) pbrt_interior (prim), string preset (prim), string g (prim), float scale (prim), float[3] sigma_a (prim), float[3] sigma_s (prim), float[3] Args: prims (list of hou.Prims): Input prims paramset (ParamSet): Any base params to add to the shape. (Optional) properties (dict): Dictionary of SohoParms (Optional) Returns: None """ # NOTE: Overlapping heterogeneous volumes don't currently # appear to be supported, although this may be an issue # with the Medium interface order? Visually it appears one # object is blocking the other. # NOTE: Not all samplers support heterogeneous volumes. Determine which # ones do, (and verify this is accurate). if properties is None: properties = {} if "pbrt_ignorevolumes" in properties and properties[ "pbrt_ignorevolumes"].Value[0]: api.Comment("Ignoring volumes because pbrt_ignorevolumes is enabled") return medium_paramset = ParamSet() if "pbrt_interior" in properties: interior = BaseNode.from_node(properties["pbrt_interior"].Value[0]) if interior is not None and interior.directive_type == "pbrt_medium": medium_paramset |= interior.paramset # These are special overrides that come from full point instancing. # It allows "per point" medium values to be "stamped" out to volume prims. interior_paramset = properties.get(".interior_overrides") if interior_paramset is not None: medium_paramset.update(interior_paramset) medium_suffix = "" instance_info = properties.get(".instance_info") if instance_info is not None: medium_suffix = ":%s[%i]" % (instance_info.source, instance_info.number) exterior = None if "pbrt_exterior" in properties: exterior = properties["pbrt_exterior"].Value[0] exterior = "" if exterior is None else exterior for prim in prims: smoke_paramset = ParamSet() medium_name = "%s[%i]%s" % ( properties["object:soppath"].Value[0], prim.number(), medium_suffix, ) resolution = prim.resolution() # TODO: Benchmark this vs other methods like fetching volumeSlices voxeldata = array.array("f") voxeldata.fromstring(prim.allVoxelsAsString()) smoke_paramset.add(PBRTParam("integer", "nx", resolution[0])) smoke_paramset.add(PBRTParam("integer", "ny", resolution[1])) smoke_paramset.add(PBRTParam("integer", "nz", resolution[2])) smoke_paramset.add(PBRTParam("point", "p0", [-1, -1, -1])) smoke_paramset.add(PBRTParam("point", "p1", [1, 1, 1])) smoke_paramset.add(PBRTParam("float", "density", voxeldata)) medium_prim_overrides = medium_prim_paramset(prim, medium_paramset) smoke_paramset.update(medium_prim_overrides) smoke_paramset |= paramset # By default we'll set a sigma_a and sigma_s to be more Houdini-like # however the object's pbrt_interior, or prim's pbrt_interior # or prim attribs will override these. if (PBRTParam("color", "sigma_a") not in smoke_paramset and PBRTParam( "color", "sigma_s") not in smoke_paramset) and PBRTParam( "string", "preset") not in smoke_paramset: smoke_paramset.add(PBRTParam("color", "sigma_a", [1, 1, 1])) smoke_paramset.add(PBRTParam("color", "sigma_s", [1, 1, 1])) with api.AttributeBlock(): xform = prim_transform(prim) api.ConcatTransform(xform) api.MakeNamedMedium(medium_name, "heterogeneous", smoke_paramset) api.Material("none") api.MediumInterface(medium_name, exterior) # Pad this slightly? bounds_to_api_box([-1, 1, -1, 1, -1, 1]) return
def smoke_prim_wrangler(prims, paramset=None, properties=None): """Outputs a "heterogeneous" Medium and bounding Shape for the input geometry The following attributes are checked for via medium_prim_paramset() - (See pbrt_medium node for what each parm does) pbrt_interior (prim), string preset (prim), string g (prim), float scale (prim), float[3] sigma_a (prim), float[3] sigma_s (prim), float[3] Args: prims (list of hou.Prims): Input prims paramset (ParamSet): Any base params to add to the shape. (Optional) properties (dict): Dictionary of SohoParms (Optional) Returns: None """ # NOTE: Overlapping heterogeneous volumes don't currently # appear to be supported, although this may be an issue # with the Medium interface order? Visually it appears one # object is blocking the other. # NOTE: Not all samplers support heterogeneous volumes. Determine which # ones do, (and verify this is accurate). if properties is None: properties = {} if ('pbrt_ignorevolumes' in properties and properties['pbrt_ignorevolumes'].Value[0]): api.Comment('Ignoring volumes because pbrt_ignorevolumes is enabled') return medium_paramset = ParamSet() if 'pbrt_interior' in properties: interior = BaseNode.from_node(properties['pbrt_interior'].Value[0]) if interior is not None and interior.directive_type == 'pbrt_medium': medium_paramset |= interior.paramset exterior = None if 'pbrt_exterior' in properties: exterior = properties['pbrt_exterior'].Value[0] exterior = '' if exterior is None else exterior for prim in prims: smoke_paramset = ParamSet() name = '%s[%i]' % (properties['object:soppath'].Value[0], prim.number()) resolution = prim.resolution() # TODO: Benchmark this vs other methods like fetching volumeSlices voxeldata = array.array('f') voxeldata.fromstring(prim.allVoxelsAsString()) smoke_paramset.add(PBRTParam('integer', 'nx', resolution[0])) smoke_paramset.add(PBRTParam('integer', 'ny', resolution[1])) smoke_paramset.add(PBRTParam('integer', 'nz', resolution[2])) smoke_paramset.add(PBRTParam('point', 'p0', [-1, -1, -1])) smoke_paramset.add(PBRTParam('point', 'p1', [1, 1, 1])) smoke_paramset.add(PBRTParam('float', 'density', voxeldata)) medium_prim_overrides = medium_prim_paramset(prim, medium_paramset) smoke_paramset.update(medium_prim_overrides) smoke_paramset |= paramset # By default we'll set a sigma_a and sigma_s to be more Houdini-like # however the object's pbrt_interior, or prim's pbrt_interior # or prim attribs will override these. if ((PBRTParam('color', 'sigma_a') not in smoke_paramset and PBRTParam('color', 'sigma_s') not in smoke_paramset) and PBRTParam('string', 'preset') not in smoke_paramset): smoke_paramset.add(PBRTParam('color', 'sigma_a', [1, 1, 1])) smoke_paramset.add(PBRTParam('color', 'sigma_s', [1, 1, 1])) with api.AttributeBlock(): xform = prim_transform(prim) api.ConcatTransform(xform) api.MakeNamedMedium(name, 'heterogeneous', smoke_paramset) api.Material('none') api.MediumInterface(name, exterior) # Pad this slightly? bounds_to_api_box([-1, 1, -1, 1, -1, 1]) return