Beispiel #1
0
def output_instances(obj, wrangler, now):
    """Define any instances referenced by the Soho Object

    This method takes an object and based on its parms and point attributes
    will iterate over any found instances and output them so they can be
    later referenced.
    """
    instances = list_instances(obj)
    if not instances:
        return

    for instance in instances:
        if instance in scene_state.instanced_geo:
            continue
        scene_state.instanced_geo.add(instance)

        # Since a referenced geo might not be displayed, output its
        # mediums if any.
        # TODO this works but is a bit magic, rethink this and see if there
        # is a better approach.
        instance_obj = soho.getObject(instance)
        output_materials(instance_obj, wrangler, now)
        output_mediums(instance_obj, wrangler, now)

        with api.ObjectBlock(instance), api.AttributeBlock():
            soho_obj = soho.getObject(instance)
            wrangle_obj(soho_obj, wrangler, now)
        print()
    return
Beispiel #2
0
def output_instances(obj, wrangler, now):
    """Define any instances referenced by the Soho Object

    This method takes an object and based on its parms and point attributes
    will iterate over any found instances and output them so they can be
    later referenced.
    """

    for instance in find_referenced_instances(obj):
        if instance in scene_state.instanced_geo:
            # If we've already emitted this reference geometry
            # then continue so we don't have duplicate definitions
            # this can happen if multiple instance nodes reference
            # the same geo
            continue
        scene_state.instanced_geo.add(instance)

        # Since a referenced geo might not be displayed, output its
        # mediums if any.
        # TODO this works but is a bit magic, rethink this and see if there
        # is a better approach.
        instance_obj = soho.getObject(instance)
        output_materials(instance_obj, wrangler, now)
        output_mediums(instance_obj, wrangler, now)

        with api.ObjectBlock(instance), api.AttributeBlock():
            soho_obj = soho.getObject(instance)
            wrangle_obj(soho_obj, wrangler, now)
        print()
    return
Beispiel #3
0
def outputMaterial(shop_path, now):
    if _Settings.SavedMaterials.get(shop_path, None) == None:
        shop = soho.getObject(shop_path)
        ray_start('material')
        outputObject(shop, now, name=shop_path, output_shader=True)
        if _Settings.GenerateMaterialname:
            ray_property('object', 'materialname', [shop_path])
        ray_end()
        _Settings.SavedMaterials[shop_path] = True
Beispiel #4
0
def outputMaterial(shop_path, now, skip='default'):
    '''Dirty way to support at least principle (disney) material.'''
    if shop_path == skip:
        return None
    material = soho.getObject(shop_path)
    obj = hou.node(shop_path)
    if not obj and shop_path != skip:
        raise Exception("This should not happen.")
    #
    if obj.type().name().startswith('principledshader::'):
        return outputPrincipledShader(obj, now)
Beispiel #5
0
def get_full_instance_info(obj, now):
    tokens = obj.getName().split(":")
    if len(tokens) != 3:
        return None
    instancer_obj = soho.getObject(tokens[1])
    instancer_sop = []
    if not instancer_obj.evalString("object:soppath", now, instancer_sop):
        return None
    instancer_sop = instancer_sop[0]
    gdp = SohoGeometry(instancer_sop, now)
    if gdp is None:
        return None
    return _FullInstance(tokens[0], tokens[1], int(tokens[2]), gdp)
def output_materials(obj, wrangler, now):
    """Output Materials for an object

    The shop_materialpath parameter and shop_materialpath prim attribute
    are both checked for output.
    """
    # We use a shaderhandle instead of a string so Soho instances are properly
    # resolved when Full Instancing is used.
    parms = [soho.SohoParm('shop_materialpath', 'shaderhandle', skipdefault=False)]
    eval_parms = obj.evaluate(parms, now)
    if eval_parms:
        shop = eval_parms[0].Value[0]
        if shop:
            wrangle_shading_network(shop)

    soppath = []
    if not obj.evalString('object:soppath', now, soppath):
        return
    soppath = soppath[0]

    gdp = SohoGeometry(soppath, now)
    global_material = gdp.globalValue('shop_materialpath')
    if global_material is not None:
        wrangle_shading_network(global_material[0])

    attrib_h = gdp.attribute('geo:prim', 'shop_materialpath')
    if attrib_h >= 0:
        shop_materialpaths = gdp.attribProperty(attrib_h, 'geo:allstrings')
        for shop in shop_materialpaths:
            wrangle_shading_network(shop)

    # TODO / CONSIDER, for very large number of instance objects it might speed things
    #   up to cache the fact we've already visited a source network. Store in scenestate?
    #   (This will avoid much of the below on a per instance basis)
    instance_info = get_full_instance_info(obj)
    if instance_info is not None:
        instance_source = soho.getObject(instance_info.source)
        sourcesop_path = []
        if not instance_source.evalString('object:soppath', now, sourcesop_path):
            return
        sourcesop_path = sourcesop_path[0]
        gdp = SohoGeometry(sourcesop_path, now)
        attrib_h = gdp.attribute('geo:point', 'shop_materialpath')
        if attrib_h >= 0:
            shop_materialpaths = gdp.attribProperty(attrib_h, 'geo:allstrings')
            for shop in shop_materialpaths:
                wrangle_shading_network(shop)
    return
Beispiel #7
0
def getObjectShader(shop_path, shader_type, now):
    shop = soho.getObject(shop_path)
    print dir(shop)
    print shop.getDefaultedShader()
    skiplist = shop.evaluate(oshaderSkipParms, now)
    shader_prop = oshaderMap[shader_type]

    skip = False
    if skiplist and shader_prop in skiplist:
        skip = skiplist[shader_prop].Value[0]

    # if not skip: # TODO disabling it intentionally
    shader = []
    shop_type = []
    if shop.evalShaderAndType(shader_type, now, shader, shop_type):
        return (shader[0], shop_type[0])

    return None
def process_full_pt_instance_material(obj, now):

    # The order of evaluation.
    #   1. shaders attached to the actual prims (handled in PBRTgeo.py)
    #   2. per point assignments on the instancer
    #   3. instancer's shop_materialpath
    #   4. object being instanced shop_materialpath
    #   5. nothing
    #   The choice between 3 and 4 is handled automatically by soho

    full_instance_info = get_full_instance_info(obj)
    instancer_obj = soho.getObject(full_instance_info.source)
    instancer_sop = []
    if not instancer_obj.evalString('object:soppath', now, instancer_sop):
        return False
    instancer_sop = instancer_sop[0]
    gdp = SohoGeometry(instancer_sop, now)

    override_attrib_h = gdp.attribute('geo:point', 'material_overrides')
    shop_attrib_h = gdp.attribute('geo:point', 'shop_materialpath')

    if shop_attrib_h < 0:
        return False

    # We can just reference a NamedMaterial since there are no overrides
    if shop_attrib_h >= 0 and override_attrib_h < 0:
        shop = gdp.value(shop_attrib_h, full_instance_info.number)[0]
        if shop in scene_state.shading_nodes:
            api.NamedMaterial(shop)
        else:
            raise ValueError('Could not find shop in scene state')
        return True

    # Fully expand shading network since there will be uniqueness
    #return
    #wrangle_shading_network(shop,
    #                        use_named=False,
    #                        saved_nodes=set()
    #                       )

    return False
Beispiel #9
0
def wrangle_light(light, wrangler, now):


    # NOTE: Lights do not support motion blur so we disable it when
    #       outputs the xforms

    node = wrangle_node_parm(light, 'light_node', now)

    parm_selection = {
        'light_wrangler' : SohoPBRT('light_wrangler', 'string', [''], False),
        'light_color' : SohoPBRT('light_color', 'float', [1, 1, 1], False),
        'light_intensity' : SohoPBRT('light_intensity', 'float', [1], False),
        'light_exposure' : SohoPBRT('light_exposure', 'float', [0], False),
    }
    parms = light.evaluate(parm_selection, now)
    light_wrangler = parms['light_wrangler'].Value[0]

    paramset = ParamSet()
    paramset.add(_to_light_scale(parms))

    if light_wrangler == 'HoudiniEnvLight':
        env_map = []
        paramset.add(PBRTParam('rgb', 'L', parms['light_color'].Value))
        if light.evalString('env_map', now, env_map):
            paramset.add(PBRTParam('string', 'mapname', env_map))
        output_xform(light, now, no_motionblur=True)
        api.Scale(1, 1, -1)
        api.Rotate(90, 0, 0, 1)
        api.Rotate(90, 0, 1, 0)
        _light_api_wrapper('infinite', paramset, node)
        return
    elif light_wrangler != 'HoudiniLight':
        api.Comment('This light type, %s, is unsupported' % light_wrangler)
        return

    # We are dealing with a standard HoudiniLight type.

    light_type = light.wrangleString(wrangler, 'light_type', now, ['point'])[0]

    if light_type in ('sphere', 'disk', 'grid', 'tube', 'geo'):

        single_sided = light.wrangleInt(wrangler, 'singlesided', now, [0])[0]
        visible = light.wrangleInt(wrangler, 'light_contribprimary', now, [0])[0]
        size = light.wrangleFloat(wrangler, 'areasize', now, [1, 1])
        paramset.add(PBRTParam('rgb', 'L', parms['light_color'].Value))
        paramset.add(PBRTParam('bool', 'twosided', [not single_sided]))

        # TODO, Possibly get the xform's scale and scale the geo, not the light.
        #       (for example, further multiplying down the radius)
        xform = get_transform(light, now)
        xform_to_api_srt(xform, scale=False)

        _light_api_wrapper('diffuse', paramset, node)

        api.AttributeBegin()

        # PBRT only supports uniform scales for non-mesh area lights
        # this is in part due to explicit light's area scaling factor.
        if light_type in ('grid', 'geo'):
            api.Scale(size[0], size[1], size[0])

        # The visibility only applies to hits on the non-emissive side of the light.
        # the emissive side will still be rendered
        if not visible:
            api.Material('none')

        if light_type == 'sphere':
            # We apply the scale to the radius instead of using a api.Scale
            api.Shape('sphere', [PBRTParam('float', 'radius', 0.5*size[0])])
        elif light_type == 'tube':
            api.Rotate(90, 0, 1, 0)
            api.Shape('cylinder', [PBRTParam('float', 'radius', 0.075*size[1]),
                                   PBRTParam('float', 'zmin', -0.5*size[0]),
                                   PBRTParam('float', 'zmax', 0.5*size[0])])
        elif light_type == 'disk':
            # A bug was introduced with Issue #154 which requires a -z scale
            # on disk area lights
            # See issue #183
            # api.Scale(1,1,-1)
            api.Shape('disk', [PBRTParam('float', 'radius', 0.5*size[0])])
        elif light_type == 'grid':
            api.Shape('trianglemesh', [PBRTParam('integer', 'indices', [0, 3, 1,
                                                                        0, 2, 3]),
                                       PBRTParam('point', 'P', [-0.5, -0.5, 0,
                                                                0.5, -0.5, 0,
                                                                -0.5, 0.5, 0,
                                                                0.5, 0.5, 0])])
        elif light_type == 'geo':
            areageo_parm = hou.node(light.getName()).parm('areageometry')
            if not areageo_parm:
                api.Comment('No "areageometry" parm on light')
                return
            area_geo_node = areageo_parm.evalAsNode()
            if not area_geo_node:
                api.Comment('Skipping, no geometry object specified')
                return
            obj = soho.getObject(area_geo_node.path())
            api.Comment('Light geo from %s' % obj.getName())
            # TODO: The area light scale ('areasize') happens *after* the wrangle_obj's xform
            #       when 'intothisobject' is enabled.
            into_this_obj = light.wrangleInt(wrangler, 'intothisobject', now, [0])[0]
            ignore_xform = not into_this_obj
            wrangle_obj(obj, None, now, ignore_xform=ignore_xform)

        api.AttributeEnd()

        return

    cone_enable = light.wrangleInt(wrangler, 'coneenable', now, [0])[0]
    projmap = light.wrangleString(wrangler, 'projmap', now, [''])[0]
    areamap = light.wrangleString(wrangler, 'areamap', now, [''])[0]

    api_calls = []
    api_calls.append(_apiclosure(output_xform, light, now, no_motionblur=True))
    api_calls.append(_apiclosure(api.Scale, 1, 1, -1))
    api_calls.append(_apiclosure(api.Scale, 1, -1, 1))

    if light_type == 'point':
        paramset.add(PBRTParam('rgb', 'I', parms['light_color'].Value))
        if areamap:
            light_name = 'goniometric'
            paramset.add(PBRTParam('string', 'mapname', [areamap]))
            api_calls = []
            api_calls.append(_apiclosure(output_xform, light, now, no_motionblur=True))
            api_calls.append(_apiclosure(api.Scale, 1, -1, 1))
            api_calls.append(_apiclosure(api.Rotate, 90, 0, 1, 0))
        elif not cone_enable:
            light_name = 'point'
        else:
            conedelta = light.wrangleFloat(wrangler, 'conedelta', now, [10])[0]
            coneangle = light.wrangleFloat(wrangler, 'coneangle', now, [45])[0]
            if projmap:
                light_name = 'projection'
                paramset.add(PBRTParam('float', 'fov', [coneangle]))
                paramset.add(PBRTParam('string', 'mapname', [projmap]))
            else:
                light_name = 'spot'
                coneangle *= 0.5
                coneangle += conedelta
                paramset.add(PBRTParam('float', 'coneangle', [coneangle]))
                paramset.add(PBRTParam('float', 'conedeltaangle', [conedelta]))
    elif light_type == 'distant':
        light_name = light_type
        paramset.add(PBRTParam('rgb', 'L', parms['light_color'].Value))
    else:
        api.Comment('Light Type, %s, not supported' % light_type)
        return

    for api_call in api_calls:
        api_call()
    _light_api_wrapper(light_name, paramset, node)

    return
def wrangle_light(light, wrangler, now):

    # NOTE: Lights do not support motion blur so we disable it when
    #       outputs the xforms

    node = wrangle_node_parm(light, "light_node", now)

    parm_selection = {
        "light_wrangler": SohoPBRT("light_wrangler", "string", [""], False),
        "light_color": SohoPBRT("light_color", "float", [1, 1, 1], False),
        "light_intensity": SohoPBRT("light_intensity", "float", [1], False),
        "light_exposure": SohoPBRT("light_exposure", "float", [0], False),
    }
    parms = light.evaluate(parm_selection, now)
    light_wrangler = parms["light_wrangler"].Value[0]

    paramset = ParamSet()
    paramset.add(_to_light_scale(parms))

    if light_wrangler == "HoudiniEnvLight":
        env_map = []
        paramset.add(PBRTParam("rgb", "L", parms["light_color"].Value))
        if light.evalString("env_map", now, env_map):
            paramset.add(PBRTParam("string", "mapname", env_map))
        output_xform(light, now, no_motionblur=True)
        api.Scale(1, 1, -1)
        api.Rotate(90, 0, 0, 1)
        api.Rotate(90, 0, 1, 0)
        _light_api_wrapper("infinite", paramset, node)
        return
    elif light_wrangler != "HoudiniLight":
        api.Comment("This light type, %s, is unsupported" % light_wrangler)
        return

    # We are dealing with a standard HoudiniLight type.

    light_type = light.wrangleString(wrangler, "light_type", now, ["point"])[0]

    if light_type in ("sphere", "disk", "grid", "tube", "geo"):

        single_sided = light.wrangleInt(wrangler, "singlesided", now, [0])[0]
        visible = light.wrangleInt(wrangler, "light_contribprimary", now, [0])[0]
        size = light.wrangleFloat(wrangler, "areasize", now, [1, 1])
        paramset.add(PBRTParam("rgb", "L", parms["light_color"].Value))
        paramset.add(PBRTParam("bool", "twosided", [not single_sided]))

        # TODO, Possibly get the xform's scale and scale the geo, not the light.
        #       (for example, further multiplying down the radius)
        xform = get_transform(light, now)
        xform_to_api_srt(xform, scale=False)

        _light_api_wrapper("diffuse", paramset, node)

        api.AttributeBegin()

        # PBRT only supports uniform scales for non-mesh area lights
        # this is in part due to explicit light's area scaling factor.
        if light_type in ("grid", "geo"):
            api.Scale(size[0], size[1], size[0])

        # The visibility only applies to hits on the non-emissive side of the light.
        # the emissive side will still be rendered
        if not visible:
            api.Material("none")

        if light_type == "sphere":
            # We apply the scale to the radius instead of using a api.Scale
            api.Shape("sphere", [PBRTParam("float", "radius", 0.5 * size[0])])
        elif light_type == "tube":
            api.Rotate(90, 0, 1, 0)
            api.Shape(
                "cylinder",
                [
                    PBRTParam("float", "radius", 0.075 * size[1]),
                    PBRTParam("float", "zmin", -0.5 * size[0]),
                    PBRTParam("float", "zmax", 0.5 * size[0]),
                ],
            )
        elif light_type == "disk":
            # After pbrt-v3 commit #2f0852ce api.ReverseOrientation() is needed,
            # prior that it was a api.Scale(1,1,-1)
            # (see issue #183 in pbrt-v3)
            api.ReverseOrientation()
            api.Shape("disk", [PBRTParam("float", "radius", 0.5 * size[0])])
        elif light_type == "grid":
            api.Shape(
                "trianglemesh",
                [
                    PBRTParam("integer", "indices", [0, 3, 1, 0, 2, 3]),
                    PBRTParam(
                        "point",
                        "P",
                        [-0.5, -0.5, 0, 0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, 0.5, 0],
                    ),
                ],
            )
        elif light_type == "geo":
            areageo_parm = hou.node(light.getName()).parm("areageometry")
            if not areageo_parm:
                api.Comment('No "areageometry" parm on light')
                return
            area_geo_node = areageo_parm.evalAsNode()
            if not area_geo_node:
                api.Comment("Skipping, no geometry object specified")
                return
            obj = soho.getObject(area_geo_node.path())
            api.Comment("Light geo from %s" % obj.getName())
            # TODO: The area light scale ('areasize') happens *after* the wrangle_obj's
            #       xform when 'intothisobject' is enabled.
            into_this_obj = light.wrangleInt(wrangler, "intothisobject", now, [0])[0]
            ignore_xform = not into_this_obj
            wrangle_obj(obj, None, now, ignore_xform=ignore_xform)

        api.AttributeEnd()

        return

    cone_enable = light.wrangleInt(wrangler, "coneenable", now, [0])[0]
    projmap = light.wrangleString(wrangler, "projmap", now, [""])[0]
    areamap = light.wrangleString(wrangler, "areamap", now, [""])[0]

    api_calls = []
    api_calls.append(_apiclosure(output_xform, light, now, no_motionblur=True))
    api_calls.append(_apiclosure(api.Scale, 1, 1, -1))
    api_calls.append(_apiclosure(api.Scale, 1, -1, 1))

    if light_type == "point":
        paramset.add(PBRTParam("rgb", "I", parms["light_color"].Value))
        if areamap:
            light_name = "goniometric"
            paramset.add(PBRTParam("string", "mapname", [areamap]))
            api_calls = []
            api_calls.append(_apiclosure(output_xform, light, now, no_motionblur=True))
            api_calls.append(_apiclosure(api.Scale, 1, -1, 1))
            api_calls.append(_apiclosure(api.Rotate, 90, 0, 1, 0))
        elif not cone_enable:
            light_name = "point"
        else:
            conedelta = light.wrangleFloat(wrangler, "conedelta", now, [10])[0]
            coneangle = light.wrangleFloat(wrangler, "coneangle", now, [45])[0]
            if projmap:
                light_name = "projection"
                paramset.add(PBRTParam("float", "fov", [coneangle]))
                paramset.add(PBRTParam("string", "mapname", [projmap]))
            else:
                light_name = "spot"
                coneangle *= 0.5
                coneangle += conedelta
                paramset.add(PBRTParam("float", "coneangle", [coneangle]))
                paramset.add(PBRTParam("float", "conedeltaangle", [conedelta]))
    elif light_type == "distant":
        light_name = light_type
        paramset.add(PBRTParam("rgb", "L", parms["light_color"].Value))
    else:
        api.Comment("Light Type, %s, not supported" % light_type)
        return

    for api_call in api_calls:
        api_call()
    _light_api_wrapper(light_name, paramset, node)

    return
Beispiel #11
0
Datei: APS.py Projekt: symek/haps
excludefog = objparms['excludefog'].Value[0]
sololight = objparms['sololight'].Value[0]
matte_objects = objparms['matte_objects'].Value[0]
phantom_objects = objparms['phantom_objects'].Value[0]
forcelightsparm = 'forcelights'
if sololight:
    stdlights = excludelights = ''
    forcelights = sololight
    forcelightsparm = 'sololight'

# Obtain the list of cameras through which we need to render. The main camera
# may specify a few sub-cameras, for example, in the stereo camera case.
camera_paths = objparms['vm_cameralist'].Value[0].split()
camera_list = []
for cam_path in camera_paths:
    camera_list.append(soho.getObject(cam_path))
if len(camera_list) == 0:
    cam.storeData('NoFileSuffix', True)
    camera_list.append(cam)

# First, we add objects based on their display flags or dimmer values
soho.addObjects(now,
                stdobject,
                stdlights,
                stdfog,
                True,
                geo_parm='vobject',
                light_parm='alights',
                fog_parm='vfog')
soho.addObjects(now,
                forceobject,
Beispiel #12
0
def wrangle_light(light, wrangler, now):

    # NOTE: Lights do not support motion blur so we disable it when
    #       outputs the xforms

    node = wrangle_node_parm(light, "light_node", now)

    # We skip the light_color if its at default so we avoid setting rgb values
    # if at all possible, that way we get a constant spectrum instead
    parm_selection = {
        "light_wrangler": SohoPBRT("light_wrangler", "string", [""], False),
        "light_color": SohoPBRT("light_color", "float", [1, 1, 1], True),
        "light_intensity": SohoPBRT("light_intensity", "float", [1], False),
        "light_exposure": SohoPBRT("light_exposure", "float", [0], False),
    }
    parms = light.evaluate(parm_selection, now)
    light_wrangler = parms["light_wrangler"].Value[0]

    exterior = light.wrangleString(wrangler, "pbrt_exterior", now, [None])[0]
    exterior = wrangle_medium(exterior)
    if exterior:
        api.MediumInterface("", exterior)
        print()

    paramset = ParamSet()
    paramset.add(_to_light_scale(parms))

    if light_wrangler == "HoudiniEnvLight":
        env_map = []
        light.evalString("env_map", now, env_map)
        # evalString will return [""] if the parm exists yet at its default
        env_map = env_map[0] if env_map else ""
        if env_map:
            paramset.add(PBRTParam("string", "filename", env_map))
        elif "light_color" in parms:
            paramset.add(PBRTParam("rgb", "L", parms["light_color"].Value))

        portal = light.wrangleString(wrangler, "env_portal", now, [""])[0]
        portal_enabled = light.wrangleInt(wrangler, "env_portalenable", now, [0])[0]
        if portal_enabled and portal:
            portal_pts = _portal_helper(now, portal)
            if portal_pts is not None:
                # TODO pbrt-v4 we may need to invert the Houdini -> PBRT xform
                paramset.add(PBRTParam("point", "portal", portal_pts))

        output_xform(light, now, no_motionblur=True)
        api.Scale(1, 1, -1)
        api.Rotate(90, 0, 0, 1)
        api.Rotate(90, 0, 1, 0)
        _light_api_wrapper("infinite", paramset, node)
        return
    elif light_wrangler != "HoudiniLight":
        api.Comment("This light type, %s, is unsupported" % light_wrangler)
        return

    # We are dealing with a standard HoudiniLight type.

    light_type = light.wrangleString(wrangler, "light_type", now, ["point"])[0]

    if light_type in ("sphere", "disk", "grid", "tube", "geo"):

        single_sided = light.wrangleInt(wrangler, "singlesided", now, [0])[0]
        reverse = light.wrangleInt(wrangler, "reverse", now, [0])[0]
        visible = light.wrangleInt(wrangler, "light_contribprimary", now, [0])[0]
        size = light.wrangleFloat(wrangler, "areasize", now, [1, 1])
        paramset.add(PBRTParam("bool", "twosided", [not single_sided]))

        texmap = light.wrangleString(wrangler, "light_texture", now, [""])[0]
        if texmap:
            paramset.add(PBRTParam("string", "filename", texmap))
        elif "light_color" in parms:
            paramset.add(PBRTParam("rgb", "L", parms["light_color"].Value))

        # TODO, Possibly get the xform's scale and scale the geo, not the light.
        #       (for example, further multiplying down the radius)
        xform = get_transform(light, now)
        xform_to_api_srt(xform, scale=False)

        _light_api_wrapper("diffuse", paramset, node)

        api.AttributeBegin()

        if single_sided and reverse:
            api.ReverseOrientation()

        shape_paramset = ParamSet()
        if not visible:
            shape_paramset.add(PBRTParam("float", "alpha", 0.0))

        # PBRT only supports uniform scales for non-mesh area lights
        # this is in part due to explicit light's area scaling factor.
        if light_type in ("grid", "geo"):
            api.Scale(size[0], size[1], size[0])

        if light_type == "sphere":
            # NOTE:
            # To match the UVs we need a api.Scale(1, 1, -1)
            # However doing this screws up the direction of emission.
            # When rendering as one sided, the emissive side will be the opposite
            # side from which is used to illuminate. Unfortunately an
            # api.ReverseOrientation() does not fix this.

            # We apply the scale to the radius instead of using a api.Scale
            shape_paramset.add(PBRTParam("float", "radius", 0.5 * size[0]))
            api.Shape("sphere", shape_paramset)
        elif light_type == "tube":
            api.Rotate(90, 0, 1, 0)
            api.Rotate(90, 0, 0, 1)
            # NOTE:
            # To match UVs we need a api.Scale(1, 1, -1)
            # see note above about spheres.
            shape_paramset.add(PBRTParam("float", "radius", 0.075 * size[1]))
            shape_paramset.add(PBRTParam("float", "zmin", -0.5 * size[0]))
            shape_paramset.add(PBRTParam("float", "zmax", 0.5 * size[0]))
            api.Shape("cylinder", shape_paramset)
        elif light_type == "disk":
            # NOTE this should match mantra now, unlike in pbrt-v3
            api.Scale(-1, 1, -1)
            shape_paramset.add(PBRTParam("float", "radius", 0.5 * size[0]))
            api.Shape("disk", shape_paramset)
        elif light_type == "grid":
            api.ReverseOrientation()
            shape_paramset.add(
                PBRTParam(
                    "point",
                    "P",
                    [-0.5, -0.5, 0, 0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, 0.5, 0],
                )
            )
            api.Shape("bilinearmesh", shape_paramset)
        elif light_type == "geo":
            areageo_parm = hou.node(light.getName()).parm("areageometry")
            if not areageo_parm:
                api.Comment('No "areageometry" parm on light')
                return
            area_geo_node = areageo_parm.evalAsNode()
            if not area_geo_node:
                api.Comment("Skipping, no geometry object specified")
                return
            obj = soho.getObject(area_geo_node.path())
            api.Comment("Light geo from %s" % obj.getName())
            # TODO: The area light scale ('areasize') happens *after* the wrangle_obj's
            #       xform when 'intothisobject' is enabled.

            # TODO: the Light visiblity paramset ("alpha") can't be easily passed
            #       with this current interface. It can be worked aroudn by setting
            #       the referenced object's "alpha" property
            into_this_obj = light.wrangleInt(wrangler, "intothisobject", now, [0])[0]
            ignore_xform = not into_this_obj
            wrangle_obj(obj, None, now, ignore_xform=ignore_xform)

        api.AttributeEnd()

        return

    cone_enable = light.wrangleInt(wrangler, "coneenable", now, [0])[0]
    projmap = light.wrangleString(wrangler, "projmap", now, [""])[0]
    areamap = light.wrangleString(wrangler, "areamap", now, [""])[0]

    api_calls = []
    api_calls.append(_apiclosure(output_xform, light, now, no_motionblur=True))
    api_calls.append(_apiclosure(api.Scale, 1, 1, -1))
    api_calls.append(_apiclosure(api.Scale, 1, -1, 1))

    if light_type == "point":
        if areamap:
            light_name = "goniometric"
            if "light_color" in parms:
                paramset.add(PBRTParam("rgb", "I", parms["light_color"].Value))
            paramset.add(PBRTParam("string", "filename", [areamap]))
            api_calls = []
            api_calls.append(_apiclosure(output_xform, light, now, no_motionblur=True))
            api_calls.append(_apiclosure(api.Scale, 1, -1, 1))
            api_calls.append(_apiclosure(api.Rotate, 90, 0, 1, 0))
        elif not cone_enable:
            light_name = "point"
            if "light_color" in parms:
                paramset.add(PBRTParam("rgb", "I", parms["light_color"].Value))
        elif projmap:
            light_name = "projection"
            coneangle = light.wrangleFloat(wrangler, "coneangle", now, [45])[0]
            paramset.add(PBRTParam("float", "fov", [coneangle]))
            paramset.add(PBRTParam("string", "filename", [projmap]))
        else:
            light_name = "spot"
            if "light_color" in parms:
                paramset.add(PBRTParam("rgb", "I", parms["light_color"].Value))
            conedelta = light.wrangleFloat(wrangler, "conedelta", now, [10])[0]
            coneangle = light.wrangleFloat(wrangler, "coneangle", now, [45])[0]
            coneangle *= 0.5
            coneangle += conedelta
            paramset.add(PBRTParam("float", "coneangle", [coneangle]))
            paramset.add(PBRTParam("float", "conedeltaangle", [conedelta]))
    elif light_type == "distant":
        light_name = light_type
        if "light_color" in parms:
            paramset.add(PBRTParam("rgb", "L", parms["light_color"].Value))
    else:
        api.Comment("Light Type, %s, not supported" % light_type)
        return

    for api_call in api_calls:
        api_call()
    _light_api_wrapper(light_name, paramset, node)

    return