Skip to content

Instantly share code, notes, and snippets.

@AngryLoki
Last active January 30, 2024 16:47

Revisions

  1. @Lockal Lockal revised this gist Dec 12, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -697,7 +697,7 @@ class ImportIES(Operator, ImportHelper):
    )

    image_format = EnumProperty(
    name='Convert to',
    name="Convert to",
    items=format_prop_items,
    default=format_prop_default,
    )
    @@ -758,7 +758,7 @@ def invoke(self, context, event):


    def menu_func(self, context):
    self.layout.operator(ImportIES.bl_idname, text='IES Lamp Data (.ies)')
    self.layout.operator(ImportIES.bl_idname, text="IES Lamp Data (.ies)")


    # Rig panel and data
  2. @Lockal Lockal revised this gist Aug 21, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -22,7 +22,7 @@
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 8),
    "blender": (2, 6, 6),
    "blender": (2, 6, 7),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    "warning": "",
  3. AngryLoki revised this gist Jun 24, 2013. 1 changed file with 38 additions and 30 deletions.
    68 changes: 38 additions & 30 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -21,7 +21,7 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 7),
    "version": (0, 8),
    "blender": (2, 6, 6),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    @@ -171,7 +171,7 @@ def read_lamp_data(log, filename, generate_rig, multiplier, image_format, color_
    if s in version_table:
    version = version_table[s]
    else:
    log({'INFO'}, 'IES file does not specify any version')
    log({'INFO'}, "IES file does not specify any version")
    version = None

    keywords = dict()
    @@ -187,38 +187,38 @@ def read_lamp_data(log, filename, generate_rig, multiplier, image_format, color_
    s, content = content.split('\n', 1)

    if not s.startswith('TILT'):
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    log({'ERROR'}, "TILT keyword not found, check your IES file")
    return {'CANCELLED'}

    # fight against ill-formed files
    file_data = content.replace(',', ' ').split()

    lamps_num = int(file_data[0])
    if lamps_num != 1:
    log({'INFO'}, 'Only 1 lamp is supported, %d in IES file' % lamps_num)
    log({'INFO'}, "Only 1 lamp is supported, %d in IES file" % lamps_num)

    lumens_per_lamp = float(file_data[1])
    candela_mult = float(file_data[2])

    v_angles_num = int(file_data[3])
    h_angles_num = int(file_data[4])
    if not v_angles_num or not h_angles_num:
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    log({'ERROR'}, "TILT keyword not found, check your IES file")
    return {'CANCELLED'}

    photometric_type = int(file_data[5])

    units_type = int(file_data[6])
    if units_type not in [1, 2]:
    log({'INFO'}, 'Units type should be either 1 (feet) or 2 (meters)')
    log({'INFO'}, "Units type should be either 1 (feet) or 2 (meters)")

    width, length, height = map(float, file_data[7:10])

    ballast_factor = float(file_data[10])

    future_use = float(file_data[11])
    if future_use != 1.0:
    log({'INFO'}, 'Invalid future use field')
    log({'INFO'}, "Invalid future use field")

    input_watts = float(file_data[12])

    @@ -231,7 +231,7 @@ def read_lamp_data(log, filename, generate_rig, multiplier, image_format, color_
    elif v_angs[0] == 0 and v_angs[-1] == 180:
    lamp_cone_type = 'TYPE180'
    else:
    log({'INFO'}, 'Lamps with vertical angles (%d-%d) are not supported' %
    log({'INFO'}, "Lamps with vertical angles (%d-%d) are not supported" %
    (v_angs[0], v_angs[-1]))
    lamp_cone_type = 'TYPE180'

    @@ -243,7 +243,7 @@ def read_lamp_data(log, filename, generate_rig, multiplier, image_format, color_
    elif abs(h_angs[0] - h_angs[-1]) == 90:
    lamp_h_type = 'TYPE90'
    else:
    log({'INFO'}, 'Lamps with horizontal angles (%d-%d) are not supported' %
    log({'INFO'}, "Lamps with horizontal angles (%d-%d) are not supported" %
    (h_angs[0], h_angs[-1]))
    lamp_h_type = 'TYPE360'

    @@ -306,7 +306,7 @@ def read_lamp_data(log, filename, generate_rig, multiplier, image_format, color_
    candela_2d = new_candela_2d

    if not h_same:
    log({'INFO'}, 'Different offsets for horizontal angles!')
    log({'INFO'}, "Different offsets for horizontal angles!")

    # normalize candela values
    maxval = max([max(row) for row in candela_2d])
    @@ -449,6 +449,7 @@ def add_img(name, intensity, lamp_cone_type, lamp_h_type, image_format,

    nt.inputs.new('NodeSocketVector', "Vector")
    nt.inputs.new('NodeSocketFloat', "Strength")
    nt.inputs.new('NodeSocketFloat', "Size")

    nt.outputs.new('NodeSocketFloat', "Intensity")

    @@ -513,10 +514,15 @@ def add_img(name, intensity, lamp_cone_type, lamp_h_type, image_format,
    nt_data_out = nt_data.outputs[0]

    nt.links.new(n0.inputs[0], nt_input.outputs[0])

    nt_intensity = nt.nodes.new('ShaderNodeMath')
    nt_intensity.operation = 'MULTIPLY'
    nt.links.new(nt_input.outputs[1], nt_intensity.inputs[0])
    nt.links.new(nt_input.outputs[2], nt_intensity.inputs[1])

    nmult = nt.nodes.new('ShaderNodeMath')
    nmult.operation = 'MULTIPLY'
    nt.links.new(nmult.inputs[0], nt_input.outputs[1])
    nt.links.new(nt_intensity.outputs[0], nmult.inputs[0])

    nt.links.new(nt_output.inputs[0], nmult.outputs[0])

    @@ -553,6 +559,7 @@ def add_img(name, intensity, lamp_cone_type, lamp_h_type, image_format,
    lnt.links.new(emission_node.inputs[1], lnt_grp.outputs[0])

    lnt_grp.inputs[1].default_value = intensity
    lnt_grp.inputs[2].default_value = 1.0

    lnt_map = lnt.nodes.new('ShaderNodeMapping')
    lnt_map.rotation[0] = pi
    @@ -563,48 +570,49 @@ def add_img(name, intensity, lamp_cone_type, lamp_h_type, image_format,
    rig_object = bpy.data.objects[rig_name]

    # add RGBA color drivers
    fcurves = lnt.driver_add(emission_node.inputs[0].path_from_id('default_value'))
    fcurves = lnt.driver_add(emission_node.inputs[0].path_from_id("default_value"))
    for i, fcurve in enumerate(fcurves):
    var = fcurve.driver.variables.new()
    var.targets[0].id = rig_object
    var.targets[0].data_path = '["ies_settings"]["color"][%d]' % i
    fcurve.driver.expression = "var"
    fcurve.driver.type = 'SUM'

    rig_object.ies_settings.color = t2rgb(color_temperature)[0:3]

    # add intensity driver
    strength_fc = lnt.driver_add(lnt_grp.inputs[1].path_from_id('default_value'))
    strength_v1 = strength_fc.driver.variables.new()
    strength_v1.name = "strength"
    strength_v1.targets[0].id = rig_object
    strength_v1.targets[0].data_path = '["ies_settings"]["strength_mult"]'

    strength_v2 = strength_fc.driver.variables.new()
    strength_v2.name = "scale"
    strength_v2.type = 'TRANSFORMS'
    strength_v2.targets[0].id = rig_object
    strength_v2.targets[0].transform_type = 'SCALE_Z'
    # add factor -> intensity driver
    strength_fc = lnt.driver_add(lnt_grp.inputs[1].path_from_id("default_value"))
    strength_fc.driver.type = 'SUM'
    strength_var = strength_fc.driver.variables.new()
    strength_var.targets[0].id = rig_object
    strength_var.targets[0].data_path = '["ies_settings"]["strength_mult"]'

    strength_fc.driver.expression = "strength * scale"
    # add size -> intensity driver
    strength_fc = lnt.driver_add(lnt_grp.inputs[2].path_from_id("default_value"))
    strength_fc.driver.type = 'SUM'
    strength_var = strength_fc.driver.variables.new()
    strength_var.type = 'TRANSFORMS'
    strength_var.targets[0].id = rig_object
    strength_var.targets[0].transform_type = 'SCALE_Z'

    # set and recalculate intensity
    rig_object.ies_settings.strength_mult = intensity

    # add rotation drivers
    fcurves = lnt.driver_add(lnt_map.path_from_id('rotation'))
    fc_types = ['ROT_X', 'ROT_Y', 'ROT_Z']
    fc_exprs = ['var', 'var', 'pi - var']
    fc_coeffs = [[0.0, 1.0], [0.0, 1.0], [pi, -1.0]]

    for fcurve, trans_type, expr in zip(fcurves, fc_types, fc_exprs):
    for fcurve, trans_type, coeffs in zip(fcurves, fc_types, fc_coeffs):
    v = fcurve.driver.variables.new()
    v.type = 'TRANSFORMS'
    v.targets[0].id = rig_object
    v.targets[0].transform_type = trans_type
    fcurve.driver.expression = expr
    fcurve.driver.type = 'SUM'
    fcurve.modifiers[0].coefficients = coeffs

    # recalculate driver data by changing rig angle
    rig_object.rotation_euler.x = pi



    lnt_geo = lnt.nodes.new('ShaderNodeNewGeometry')
    lnt.links.new(lnt_map.inputs[0], lnt_geo.outputs[1])
  4. AngryLoki revised this gist Apr 28, 2013. 1 changed file with 346 additions and 69 deletions.
    415 changes: 346 additions & 69 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -21,7 +21,7 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 5),
    "version": (0, 7),
    "blender": (2, 6, 6),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    @@ -32,20 +32,20 @@
    }

    import bpy
    import bmesh
    import mathutils
    import os

    from math import log, pow, pi
    from math import pi
    from operator import add, truediv



    def clamp(x, min, max):
    if x < min:
    return min
    elif x > max:
    return max
    return x


    # Temperature to RGB
    # OSL version:
    # http://blenderartists.org/forum/showthread.php?270332&p=2268693#post2268693
    @@ -72,15 +72,95 @@ def simple_interp(k, x, y):
    return y[i] + (k - x[i]) * (y[i - 1] - y[i]) / (x[i - 1] - x[i])


    def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    def gen_rig_object(name, data):
    mesh = bpy.data.meshes.new('lamp rig ' + name)
    bm = bmesh.new()

    scale = 1.0

    first = bm.verts.new((0, 0, data[0][0] * scale))
    last = bm.verts.new((0, 0, -data[0][-1] * scale))

    v_angles = [pi * (i+1)/(len(data[0])-1) for i in range(len(data[0])-2)]
    h_angles = [2 * pi * i/(len(data)) for i in range(len(data))]

    for h_angle, angle_data in zip(h_angles, data):
    verts = []
    for v_angle, value in zip(v_angles, angle_data[1:-1]):
    vec = mathutils.Vector((0.0, 0.0, 1.0))
    vec.rotate(mathutils.Euler((v_angle, 0.0, h_angle - pi / 2), 'XYZ'))
    verts.append(bm.verts.new((vec * value * scale)))

    for i in range(len(verts)-1):
    bm.edges.new((verts[i], verts[i+1]))

    bm.edges.new((first, verts[0]))
    bm.edges.new((last, verts[-1]))

    bm.to_mesh(mesh)
    mesh.update()

    ob = bpy.data.objects.new('lamp rig ' + name, mesh)
    ob.location = bpy.context.scene.cursor_location
    bpy.context.scene.objects.link(ob)

    return ob.name


    def reinterpolate_line(x_data, y_data, new_width):
    new_x_data = [i/(new_width-1) for i in range(new_width)]
    new_y_data = [simple_interp(k, x_data, y_data) for k in new_x_data]
    return new_y_data


    # default number of vertices for vertical and horizontal directions in rig
    rig_v = {'TYPE90': 10, 'TYPE180': 20}
    rig_h = {'TYPE90': 16, 'TYPE180': 16}


    def gen_vcurves_rig(name, x_data, y_data, cone_type):
    new_data = [reinterpolate_line(x_data, y_data, rig_v[cone_type])] * rig_h[cone_type]
    return gen_rig_object(name, new_data)


    def reinterpolate_2d(data, size, h_type):
    if h_type == 'TYPE90' or h_type == 'TYPE180':
    data += list(reversed(data))[1:]

    if h_type == 'TYPE90':
    data += list(reversed(data))[1:]

    if len(data) == 1:
    data = [data[0]] * 2

    for length in size:
    x_data = [i / (len(data[0]) - 1) for i in range(len(data[0]))]
    for i in range(len(data)):
    data[i] = reinterpolate_line(x_data, data[i], length)
    data = list(zip(*data))

    return data


    def gen_2d_rig(name, data, cone_type, h_type):
    # reinterpolate a deep copy of data
    new_size = [rig_v[cone_type], rig_h[cone_type]]
    new_data = reinterpolate_2d(data[:], new_size, h_type)
    return gen_rig_object(name, new_data)


    def read_lamp_data(log, filename, generate_rig, multiplier, image_format, color_temperature):
    # log({'INFO'}, 'Start IES import')
    rig_name = ''

    version_table = {
    'IESNA:LM-63-1986': 1986,
    'IESNA:LM-63-1991': 1991,
    'IESNA91': 1991,
    'IESNA:LM-63-1995': 1995,
    'IESNA:LM-63-2002': 2002,
    }

    name = os.path.splitext(os.path.split(filename)[1])[0]

    file = open(filename, 'rt', encoding='cp1252')
    @@ -91,7 +171,7 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    if s in version_table:
    version = version_table[s]
    else:
    log({'DEBUG'}, 'IES file does not specify any version')
    log({'INFO'}, 'IES file does not specify any version')
    version = None

    keywords = dict()
    @@ -108,13 +188,14 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):

    if not s.startswith('TILT'):
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    return {'CANCELED'}
    return {'CANCELLED'}

    # fight against ill-formed files
    file_data = content.replace(',', ' ').split()

    lamps_num = int(file_data[0])
    #if lamps_num != 1.0:
    # print('Only 1 lamp is supported at this moment')
    if lamps_num != 1:
    log({'INFO'}, 'Only 1 lamp is supported, %d in IES file' % lamps_num)

    lumens_per_lamp = float(file_data[1])
    candela_mult = float(file_data[2])
    @@ -123,23 +204,21 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    h_angles_num = int(file_data[4])
    if not v_angles_num or not h_angles_num:
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    return {'CANCELED'}
    return {'CANCELLED'}

    photometric_type = int(file_data[5])

    units_type = int(file_data[6])
    #if units_type not in [1, 2]:
    # print('Units type should be either 1 (feet) or 2 (meters)')
    if units_type not in [1, 2]:
    log({'INFO'}, 'Units type should be either 1 (feet) or 2 (meters)')

    width = float(file_data[7])
    length = float(file_data[8])
    height = float(file_data[9])
    width, length, height = map(float, file_data[7:10])

    ballast_factor = float(file_data[10])

    future_use = float(file_data[11])
    if future_use != 1.0:
    print('Invalid future use field')
    log({'INFO'}, 'Invalid future use field')

    input_watts = float(file_data[12])

    @@ -152,36 +231,58 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    elif v_angs[0] == 0 and v_angs[-1] == 180:
    lamp_cone_type = 'TYPE180'
    else:
    log({'DEBUG'}, 'Lamps with vertical angles (%d-%d) are not supported' %
    log({'INFO'}, 'Lamps with vertical angles (%d-%d) are not supported' %
    (v_angs[0], v_angs[-1]))
    lamp_cone_type = 'TYPE180'


    if len(h_angs) == 1 or abs(h_angs[0] - h_angs[-1]) == 360:
    lamp_h_type = 'TYPE360'
    elif abs(h_angs[0] - h_angs[-1]) == 180:
    lamp_h_type = 'TYPE180'
    elif abs(h_angs[0] - h_angs[-1]) == 90:
    lamp_h_type = 'TYPE90'
    else:
    log({'INFO'}, 'Lamps with horizontal angles (%d-%d) are not supported' %
    (h_angs[0], h_angs[-1]))
    lamp_h_type = 'TYPE360'

    # print(h_angs, lamp_h_type)

    # read candela values
    offset = 13 + len(v_angs) + len(h_angs)
    candela_num = len(v_angs) * len(h_angs)
    candela_values = [float(s) for s in file_data[offset:offset + candela_num]]

    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))

    if image_format == 'VCURVES':
    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))
    # scale vertical angles to [0, 1] range
    x_data = [0.5 + 0.5 * x / v_angs[-1] for x in v_angs]
    x_rig_data = [x / v_angs[-1] for x in v_angs]
    x_data = [0.5 + 0.5 * x for x in x_rig_data]

    # approximate multidimentional lamp data to single dimention
    y_data = [sum(x) / len(x) for x in zip(*candela_2d)]
    y_data_max = max(y_data)
    intensity = max(500, min(y_data_max * multiplier, 5000))
    lamp_data = list(zip(
    x_data, [0.5 + 0.5 * y / y_data_max for y in y_data]))

    intensity = max(500, min(y_data_max * multiplier * candela_mult, 5000))

    lamp_rig_y_data = [y / y_data_max for y in y_data]
    lamp_y_data = [0.5 + 0.5 * y for y in lamp_rig_y_data]

    lamp_data = list(zip(x_data, lamp_y_data))
    if generate_rig:
    rig_name = gen_vcurves_rig(name, x_rig_data, lamp_rig_y_data, lamp_cone_type)

    return add_img(name=name,
    intensity=intensity,
    lamp_cone_type=lamp_cone_type,
    lamp_h_type=lamp_h_type,
    image_format=image_format,
    color_temperature=color_temperature,
    lamp_data=lamp_data)

    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))
    lamp_data=lamp_data,
    rig_name=rig_name)

    # check if angular offsets are the same
    v_d = [v_angs[i] - v_angs[i - 1] for i in range(1, len(v_angs))]
    @@ -205,15 +306,26 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    candela_2d = new_candela_2d

    if not h_same:
    log({'DEBUG'}, 'Different offsets for horizontal angles!')
    log({'INFO'}, 'Different offsets for horizontal angles!')

    # normalize candela values
    maxval = max([max(row) for row in candela_2d])
    candela_2d = [[val / maxval for val in row] for row in candela_2d]

    # generate rig object
    if generate_rig:
    rig_name = gen_2d_rig(name, candela_2d, lamp_cone_type, lamp_h_type)

    # add extra left and right rows to bypass cycles repeat of uv coordinates
    candela_2d = [[line[0]] + list(line) + [line[-1]] for line in candela_2d]

    if len(candela_2d) > 1:
    candela_2d = [candela_2d[0]] + candela_2d + [candela_2d[-1]]

    # flatten 2d array to 1d
    candela_values = [y for x in candela_2d for y in x]

    maxval = max(candela_values)
    intensity = max(500, min(maxval * multiplier, 5000))

    intensity = max(500, min(maxval * multiplier * candela_mult, 5000))

    if image_format == 'PNG':
    float_buffer = False
    @@ -222,20 +334,21 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    float_buffer = True
    filepath = '//' + name + '.exr'

    img = bpy.data.images.new(name, len(v_angs) + 2, len(h_angs),
    img = bpy.data.images.new(name, len(candela_2d[0]), len(candela_2d),
    float_buffer=float_buffer)

    for i in range(len(candela_values)):
    val = candela_mult * candela_values[i] / maxval
    for i, val in enumerate(candela_values):
    img.pixels[4 * i] = img.pixels[4 * i + 1] = img.pixels[4 * i + 2] = val

    bpy.ops.import_lamp.gen_exr('INVOKE_DEFAULT',
    image_name=img.name,
    intensity=intensity,
    lamp_cone_type=lamp_cone_type,
    lamp_h_type=lamp_h_type,
    image_format=image_format,
    color_temperature=color_temperature,
    filepath=filepath)
    filepath=filepath,
    rig_name=rig_name)

    return {'FINISHED'}

    @@ -254,7 +367,7 @@ def scale_coords(nt, sock_in, sock_out, size):
    nt.links.new(sock_out, mul.outputs[0])


    def add_h_angles(nt, x, y, out):
    def add_h_angles(nt, x, y, out, lamp_h_type):
    na = nt.nodes.new('ShaderNodeMath')
    na.operation = 'MULTIPLY'
    nt.links.new(na.inputs[0], x)
    @@ -292,18 +405,40 @@ def add_h_angles(nt, x, y, out):
    nj = nt.nodes.new('ShaderNodeMath')
    nj.operation = 'DIVIDE'
    nt.links.new(nj.inputs[0], nh.outputs[0])
    nj.inputs[1].default_value = pi

    nk = nt.nodes.new('ShaderNodeMath')
    nk.operation = 'ADD'
    nt.links.new(nk.inputs[0], nj.outputs[0])
    nk.inputs[1].default_value = 0.5
    # add abs() cascade for lamps with horizontal angles from 0 to 180
    if lamp_h_type == 'TYPE90' or lamp_h_type == 'TYPE180':
    repeat_times = 4 if lamp_h_type == 'TYPE90' else 2
    nj.inputs[1].default_value = pi / repeat_times

    nk = nt.nodes.new('ShaderNodeMath')
    nk.operation = 'MULTIPLY'
    nt.links.new(nj.outputs[0], nk.inputs[0])
    nk.inputs[1].default_value = -1.0

    nl = nt.nodes.new('ShaderNodeMath')
    nl.operation = 'MAXIMUM'
    nt.links.new(nj.outputs[0], nl.inputs[0])
    nt.links.new(nk.outputs[0], nl.inputs[1])
    nt.links.new(nl.outputs[0], out)
    else:
    nj.inputs[1].default_value = pi
    nt.links.new(nj.outputs[0], out)


    nt.links.new(out, nk.outputs[0])
    def add_uv_mapping_node(nt, input, output, img_size):
    nt_map = nt.nodes.new('ShaderNodeMapping')

    for i in range(2):
    nt_map.translation[i] = 1 / (img_size[i] - 2)
    nt_map.scale[i] = (img_size[i] - 2) / img_size[i]

    nt.links.new(nt_map.inputs[0], input)
    nt.links.new(nt_map.outputs[0], output)


    def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    filepath=None, lamp_data=None):
    def add_img(name, intensity, lamp_cone_type, lamp_h_type, image_format,
    color_temperature, filepath=None, lamp_data=None, rig_name=None):
    if image_format != 'VCURVES':
    img = bpy.data.images[name]
    img.filepath_raw = filepath
    @@ -354,18 +489,29 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    nt_data_sep = nt.nodes.new('ShaderNodeSeparateRGB')
    nt.links.new(nt_data_sep.inputs[0], nt_data.outputs[0])
    nt_data_out = nt_data_sep.outputs[0]
    else:
    n2 = nt.nodes.new('ShaderNodeCombineRGB')
    scale_coords(nt, ni.outputs[0], n2.inputs[0], img.size[0])
    else: # image-based
    nt_combine = nt.nodes.new('ShaderNodeCombineRGB')

    # use (x+a)*b cascade for Nx1 images
    if img.size[1] == 1:
    scale_coords(nt, ni.outputs[0], nt_combine.inputs[0], img.size[0])
    else:
    nt.links.new(ni.outputs[0], nt_combine.inputs[0])

    if img.size[1] > 1:
    add_h_angles(nt, n0.outputs[0], n0.outputs[1], n2.inputs[1])
    add_h_angles(nt, n0.outputs[0], n0.outputs[1], nt_combine.inputs[1], lamp_h_type)

    nt_data = nt.nodes.new('ShaderNodeTexImage')
    nt_data.image = img
    nt_data.color_space = 'NONE'
    nt.links.new(nt_data.inputs[0], n2.outputs[0])

    if img.size[1] > 1:
    add_uv_mapping_node(nt, nt_combine.outputs[0], nt_data.inputs[0], img.size)
    else:
    nt.links.new(nt_combine.outputs[0], nt_data.inputs[0])

    nt_data_out = nt_data.outputs[0]


    nt.links.new(n0.inputs[0], nt_input.outputs[0])

    nmult = nt.nodes.new('ShaderNodeMath')
    @@ -390,6 +536,7 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    nt.links.new(nmult.inputs[1], nif.outputs[0])

    lampdata = bpy.data.lamps.new('Lamp ' + name, 'POINT')
    lampdata.shadow_soft_size = 0.01
    lampdata.use_nodes = True
    lnt = lampdata.node_tree

    @@ -404,30 +551,91 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,

    emission_node.inputs[0].default_value = t2rgb(color_temperature)
    lnt.links.new(emission_node.inputs[1], lnt_grp.outputs[0])

    lnt_grp.inputs[1].default_value = intensity

    lnt_map = lnt.nodes.new('ShaderNodeMapping')
    lnt_map.rotation[0] = pi
    lnt.links.new(lnt_grp.inputs[0], lnt_map.outputs[0])

    if rig_name:
    lampdata["rigged_ies"] = True
    rig_object = bpy.data.objects[rig_name]

    # add RGBA color drivers
    fcurves = lnt.driver_add(emission_node.inputs[0].path_from_id('default_value'))
    for i, fcurve in enumerate(fcurves):
    var = fcurve.driver.variables.new()
    var.targets[0].id = rig_object
    var.targets[0].data_path = '["ies_settings"]["color"][%d]' % i
    fcurve.driver.expression = "var"

    rig_object.ies_settings.color = t2rgb(color_temperature)[0:3]

    # add intensity driver
    strength_fc = lnt.driver_add(lnt_grp.inputs[1].path_from_id('default_value'))
    strength_v1 = strength_fc.driver.variables.new()
    strength_v1.name = "strength"
    strength_v1.targets[0].id = rig_object
    strength_v1.targets[0].data_path = '["ies_settings"]["strength_mult"]'

    strength_v2 = strength_fc.driver.variables.new()
    strength_v2.name = "scale"
    strength_v2.type = 'TRANSFORMS'
    strength_v2.targets[0].id = rig_object
    strength_v2.targets[0].transform_type = 'SCALE_Z'

    strength_fc.driver.expression = "strength * scale"

    rig_object.ies_settings.strength_mult = intensity

    # add rotation drivers
    fcurves = lnt.driver_add(lnt_map.path_from_id('rotation'))
    fc_types = ['ROT_X', 'ROT_Y', 'ROT_Z']
    fc_exprs = ['var', 'var', 'pi - var']

    for fcurve, trans_type, expr in zip(fcurves, fc_types, fc_exprs):
    v = fcurve.driver.variables.new()
    v.type = 'TRANSFORMS'
    v.targets[0].id = rig_object
    v.targets[0].transform_type = trans_type
    fcurve.driver.expression = expr

    # recalculate driver data by changing rig angle
    rig_object.rotation_euler.x = pi



    lnt_geo = lnt.nodes.new('ShaderNodeNewGeometry')
    lnt.links.new(lnt_map.inputs[0], lnt_geo.outputs[1])

    lamp = bpy.data.objects.new("Lamp " + name, lampdata)
    lamp.location = bpy.context.scene.cursor_location

    bpy.context.scene.objects.link(lamp)

    for ob in bpy.data.objects:
    ob.select = False

    lamp.select = True
    bpy.context.scene.objects.active = lamp

    if rig_name:
    rig_object = bpy.data.objects[rig_name]
    lamp.parent = rig_object
    lamp.lock_location[:] = [True] * 3
    lamp.lock_rotation[:] = [True] * 3
    lamp.lock_scale[:] = [True] * 3
    # lamp.hide_select = True
    rig_object.select = True
    bpy.context.scene.objects.active = rig_object
    else:
    lamp.location = bpy.context.scene.cursor_location
    bpy.context.scene.objects.active = lamp

    return {'FINISHED'}


    from bpy_extras.io_utils import ImportHelper, ExportHelper
    from bpy.props import StringProperty, FloatProperty, EnumProperty, IntProperty
    from bpy.props import StringProperty, FloatProperty, EnumProperty, IntProperty, BoolProperty
    from bpy.types import Operator

    format_prop_items = (
    @@ -437,7 +645,29 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    )

    format_prop_default = 'VCURVES'
    # format_prop_default = 'PNG'


    temperature_prop_items = (
    ('T1700', "1700K: Match flame", "Match flame"),
    ('T1850', "1850K: Candle light", "Candle light or sunlight at sunrise or sunset"),
    ('T2700', "2700K: Very Warm White", "Similar light to \"normal\" incandescent bulbs, giving a warm \"cosy\" feel"),
    ('T3000', "3000K: Warm White", "The colour of most halogen lamps. Appears slightly \"whiter\" than ordinary incandescent lamps"),
    ('T3200', "3200K: Studio Lamp", "Studio Lamps/Photofloods"),
    ('T3500', "3500K: White", "The standard colour for many fluorescent and compact fluorescent tubes"),
    ('T4000', "4000K: Cool White", "Gives a more clinical or \"high tech\" feel"),
    ('T4100', "4100K: Moonlight", "Moonlight, xenon arc lamp"),
    ('T5000', "5000K: Horizon daylight", "Horizon daylight, tubular fluorescent lamps or Cool White/Daylight compact fluorescent lamps (CFL)"),
    ('T5600', "5600K: Nominal Sunlight", "Nominal Sunlight, mid day during mid summer"),
    ('T6000', "6000K: Daylight", "Fluorescent or compact fluorescent lamps simulating natural daylight"),
    ('T6500', "6500K: Cool Daylight", "Extremely \"white\" light used in specialist daylight lamps"),
    ('T7000', "7000K: LCD/CRT screen", "LCD or CRT screen"),
    ('T8000', "8000K: LCD/CRT screen", "LCD or CRT screen"),
    ('T9000', "9000K: LCD/CRT screen", "LCD or CRT screen"),
    ('T20000', "20000K: Open Sky", "Clear blue poleward sky")
    )

    temperature_prop_default = 'T6500'

    class ImportIES(Operator, ImportHelper):
    """Import IES lamp data and generate a node group for cycles"""
    @@ -446,6 +676,12 @@ class ImportIES(Operator, ImportHelper):

    filter_glob = StringProperty(default="*.ies", options={'HIDDEN'})

    generate_rig = BoolProperty(
    name="Generate Rig",
    description="Generate rig for lamp",
    default=True,
    )

    lamp_strength = FloatProperty(
    name="Strength",
    description="Multiplier for lamp strength",
    @@ -458,16 +694,17 @@ class ImportIES(Operator, ImportHelper):
    default=format_prop_default,
    )

    color_temperature = IntProperty(
    color_temperature = EnumProperty(
    name="Color Temperature",
    description="Color temperature of lamp, 3000=soft white, "
    "5000=cool white, 6500=daylight",
    default=6500,
    description="Color temperature of lamp",
    items=temperature_prop_items,
    default=temperature_prop_default,
    )

    def execute(self, context):
    return read_lamp_data(self.report, self.filepath, self.lamp_strength,
    self.image_format, self.color_temperature)
    return read_lamp_data(self.report, self.filepath, self.generate_rig,
    self.lamp_strength, self.image_format,
    int(self.color_temperature[1:]))


    class ExportLampEXR(Operator, ExportHelper):
    @@ -478,21 +715,30 @@ class ExportLampEXR(Operator, ExportHelper):
    image_name = StringProperty(options={'HIDDEN'})
    intensity = FloatProperty(options={'HIDDEN'})
    lamp_cone_type = EnumProperty(
    items=(('TYPE90', "0-90", "Angles from 0 to 90 degrees"),
    ('TYPE180', "0-180", "Angles from 0 to 90 degrees")),
    items=(('TYPE90', "0-90", ""),
    ('TYPE180', "0-180", "")),
    options={'HIDDEN'}
    )
    lamp_h_type = EnumProperty(
    items=(('TYPE90', "0-90", ""),
    ('TYPE180', "0-180", ""),
    ('TYPE360', "0-360", "")),
    options={'HIDDEN'}
    )
    image_format = EnumProperty(items=format_prop_items, options={'HIDDEN'})
    color_temperature = IntProperty(options={'HIDDEN'})
    rig_name = StringProperty(options={'HIDDEN'})
    use_filter_image = True

    def execute(self, context):
    return add_img(name=self.image_name,
    intensity=self.intensity,
    lamp_cone_type=self.lamp_cone_type,
    lamp_h_type=self.lamp_h_type,
    image_format=self.image_format,
    color_temperature=self.color_temperature,
    filepath=self.filepath)
    filepath=self.filepath,
    rig_name=self.rig_name)

    def invoke(self, context, event):
    if self.image_format == 'PNG':
    @@ -507,16 +753,47 @@ def menu_func(self, context):
    self.layout.operator(ImportIES.bl_idname, text='IES Lamp Data (.ies)')


    # Rig panel and data
    class IesRigSettings(bpy.types.PropertyGroup):
    strength_mult = bpy.props.FloatProperty(name="Strength Multiplier",
    default=1, min=0, max=1e4)
    color = bpy.props.FloatVectorProperty(name="Color", subtype="COLOR")


    class IesRigPanel(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "Lamp Properties"

    @classmethod
    def poll(self, context):
    try:
    return context.active_object.children[0].data['rigged_ies']
    except:
    return False

    def draw(self, context):
    ob = context.active_object
    self.layout.prop(ob.ies_settings, "strength_mult")
    self.layout.prop(ob.ies_settings, "color")


    registered_classes = [IesRigSettings, IesRigPanel, ImportIES, ExportLampEXR]


    def register():
    bpy.utils.register_class(ImportIES)
    bpy.utils.register_class(ExportLampEXR)
    for cls in registered_classes:
    bpy.utils.register_class(cls)

    bpy.types.Object.ies_settings = bpy.props.PointerProperty(type=IesRigSettings)
    bpy.types.INFO_MT_file_import.append(menu_func)


    def unregister():
    bpy.utils.unregister_class(ImportIES)
    bpy.types.INFO_MT_file_import.remove(menu_func)
    for cls in registered_classes:
    bpy.utils.unregister_class(cls)

    bpy.types.INFO_MT_file_import.remove(menu_func)

    if __name__ == "__main__":
    register()
  5. AngryLoki revised this gist Apr 4, 2013. 1 changed file with 51 additions and 39 deletions.
    90 changes: 51 additions & 39 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -21,7 +21,7 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 4),
    "version": (0, 5),
    "blender": (2, 6, 6),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    @@ -241,12 +241,12 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):


    def scale_coords(nt, sock_in, sock_out, size):
    add = nt.nodes.new('MATH')
    add = nt.nodes.new('ShaderNodeMath')
    add.operation = 'ADD'
    nt.links.new(add.inputs[0], sock_in)
    add.inputs[1].default_value = 1.0 / (size - 2)

    mul = nt.nodes.new('MATH')
    mul = nt.nodes.new('ShaderNodeMath')
    mul.operation = 'MULTIPLY'
    nt.links.new(mul.inputs[0], add.outputs[0])
    mul.inputs[1].default_value = (size - 2.0) / size
    @@ -255,46 +255,46 @@ def scale_coords(nt, sock_in, sock_out, size):


    def add_h_angles(nt, x, y, out):
    na = nt.nodes.new('MATH')
    na = nt.nodes.new('ShaderNodeMath')
    na.operation = 'MULTIPLY'
    nt.links.new(na.inputs[0], x)
    nt.links.new(na.inputs[1], x)

    nb = nt.nodes.new('MATH')
    nb = nt.nodes.new('ShaderNodeMath')
    nb.operation = 'MULTIPLY'
    nt.links.new(nb.inputs[0], y)
    nt.links.new(nb.inputs[1], y)

    nc = nt.nodes.new('MATH')
    nc = nt.nodes.new('ShaderNodeMath')
    nc.operation = 'ADD'
    nt.links.new(nc.inputs[0], na.outputs[0])
    nt.links.new(nc.inputs[1], nb.outputs[0])

    nd = nt.nodes.new('MATH')
    nd = nt.nodes.new('ShaderNodeMath')
    nd.operation = 'POWER'
    nt.links.new(nd.inputs[0], nc.outputs[0])
    nd.inputs[1].default_value = 0.5

    nf = nt.nodes.new('MATH')
    nf = nt.nodes.new('ShaderNodeMath')
    nf.operation = 'ADD'
    nt.links.new(nf.inputs[0], x)
    nt.links.new(nf.inputs[1], nd.outputs[0])

    ng = nt.nodes.new('MATH')
    ng = nt.nodes.new('ShaderNodeMath')
    ng.operation = 'DIVIDE'
    nt.links.new(ng.inputs[0], y)
    nt.links.new(ng.inputs[1], nf.outputs[0])

    nh = nt.nodes.new('MATH')
    nh = nt.nodes.new('ShaderNodeMath')
    nh.operation = 'ARCTANGENT'
    nt.links.new(nh.inputs[0], ng.outputs[0])

    nj = nt.nodes.new('MATH')
    nj = nt.nodes.new('ShaderNodeMath')
    nj.operation = 'DIVIDE'
    nt.links.new(nj.inputs[0], nh.outputs[0])
    nj.inputs[1].default_value = pi

    nk = nt.nodes.new('MATH')
    nk = nt.nodes.new('ShaderNodeMath')
    nk.operation = 'ADD'
    nt.links.new(nk.inputs[0], nj.outputs[0])
    nk.inputs[1].default_value = 0.5
    @@ -310,14 +310,23 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    img.file_format = image_format
    img.save()

    nt = bpy.data.node_groups.new('Lamp ' + name, 'SHADER')
    n0 = nt.nodes.new('SEPRGB')

    ne = nt.nodes.new('MATH')
    nt = bpy.data.node_groups.new("Lamp " + name, 'ShaderNodeTree')

    nt.inputs.new('NodeSocketVector', "Vector")
    nt.inputs.new('NodeSocketFloat', "Strength")

    nt.outputs.new('NodeSocketFloat', "Intensity")

    nt_input = nt.nodes.new('NodeGroupInput')
    nt_output = nt.nodes.new('NodeGroupOutput')

    n0 = nt.nodes.new('ShaderNodeSeparateRGB')

    ne = nt.nodes.new('ShaderNodeMath')
    ne.operation = 'ARCCOSINE'
    nt.links.new(ne.inputs[0], n0.outputs[2])

    ni = nt.nodes.new('MATH')
    ni = nt.nodes.new('ShaderNodeMath')
    ni.operation = 'DIVIDE'
    nt.links.new(ni.inputs[0], ne.outputs[0])

    @@ -327,7 +336,7 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    ni.inputs[1].default_value = pi / 2

    if image_format == 'VCURVES':
    nt_data = nt.nodes.new('CURVE_VEC')
    nt_data = nt.nodes.new('ShaderNodeVectorCurve')
    nt.links.new(nt_data.inputs[1], ni.outputs[0])
    for x, y in lamp_data[:-1]:
    pt = nt_data.mapping.curves[0].points.new(x, y)
    @@ -342,40 +351,38 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    nt_data.mapping.curves[0].points[-1].location[1] = 0.5 # no light
    nt_data.mapping.curves[0].points[-1].handle_type = 'VECTOR'

    nt_data_sep = nt.nodes.new('SEPRGB')
    nt_data_sep = nt.nodes.new('ShaderNodeSeparateRGB')
    nt.links.new(nt_data_sep.inputs[0], nt_data.outputs[0])
    nt_data_out = nt_data_sep.outputs[0]
    else:
    n2 = nt.nodes.new('COMBRGB')
    n2 = nt.nodes.new('ShaderNodeCombineRGB')
    scale_coords(nt, ni.outputs[0], n2.inputs[0], img.size[0])
    if img.size[1] > 1:
    add_h_angles(nt, n0.outputs[0], n0.outputs[1], n2.inputs[1])
    nt_data = nt.nodes.new('TEX_IMAGE')
    nt_data = nt.nodes.new('ShaderNodeTexImage')
    nt_data.image = img
    nt_data.color_space = 'NONE'
    nt.links.new(nt_data.inputs[0], n2.outputs[0])
    nt_data_out = nt_data.outputs[0]

    i1 = nt.inputs.new('Vector', 'VECTOR')
    i2 = nt.inputs.new('Strength', 'VALUE')
    nt.links.new(n0.inputs[0], i1)

    nmult = nt.nodes.new('MATH')
    nt.links.new(n0.inputs[0], nt_input.outputs[0])

    nmult = nt.nodes.new('ShaderNodeMath')
    nmult.operation = 'MULTIPLY'
    nt.links.new(nmult.inputs[0], i2)
    nt.links.new(nmult.inputs[0], nt_input.outputs[1])

    o1 = nt.outputs.new('Intensity', 'VALUE')
    nt.links.new(o1, nmult.outputs[0])
    nt.links.new(nt_output.inputs[0], nmult.outputs[0])

    if lamp_cone_type == 'TYPE180' or image_format == 'VCURVES':
    nt.links.new(nmult.inputs[1], nt_data_out)
    else: # TYPE90
    nlt = nt.nodes.new('MATH')
    nlt = nt.nodes.new('ShaderNodeMath')
    nlt.operation = 'LESS_THAN'
    nt.links.new(nlt.inputs[0], ni.outputs[0])
    nlt.inputs[1].default_value = 1.0

    nif = nt.nodes.new('MATH')
    nif = nt.nodes.new('ShaderNodeMath')
    nif.operation = 'MULTIPLY'
    nt.links.new(nif.inputs[0], nt_data_out)
    nt.links.new(nif.inputs[1], nlt.outputs[0])
    @@ -386,22 +393,27 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    lampdata.use_nodes = True
    lnt = lampdata.node_tree

    #for node in lnt.nodes:
    # print(node)

    lnt_grp = lnt.nodes.new('GROUP', group=nt)
    lnt.nodes['Emission'].inputs[0].default_value = t2rgb(color_temperature)
    lnt.links.new(lnt.nodes['Emission'].inputs[1], lnt_grp.outputs[0])
    lnt_grp = lnt.nodes.new('ShaderNodeGroup')
    lnt_grp.node_tree = nt


    for node in lnt.nodes:
    if node.bl_idname == 'ShaderNodeEmission':
    emission_node = node
    break

    emission_node.inputs[0].default_value = t2rgb(color_temperature)
    lnt.links.new(emission_node.inputs[1], lnt_grp.outputs[0])
    lnt_grp.inputs[1].default_value = intensity

    lnt_map = lnt.nodes.new('MAPPING')
    lnt_map = lnt.nodes.new('ShaderNodeMapping')
    lnt_map.rotation[0] = pi
    lnt.links.new(lnt_grp.inputs[0], lnt_map.outputs[0])

    lnt_geo = lnt.nodes.new('NEW_GEOMETRY')
    lnt_geo = lnt.nodes.new('ShaderNodeNewGeometry')
    lnt.links.new(lnt_map.inputs[0], lnt_geo.outputs[1])

    lamp = bpy.data.objects.new('Lamp ' + name, lampdata)
    lamp = bpy.data.objects.new("Lamp " + name, lampdata)
    lamp.location = bpy.context.scene.cursor_location
    bpy.context.scene.objects.link(lamp)

  6. AngryLoki revised this gist Feb 24, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -22,7 +22,7 @@
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 4),
    "blender": (2, 6, 5),
    "blender": (2, 6, 6),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    "warning": "",
  7. AngryLoki revised this gist Feb 23, 2013. 1 changed file with 43 additions and 34 deletions.
    77 changes: 43 additions & 34 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -37,13 +37,18 @@
    from math import log, pow, pi
    from operator import add, truediv


    def clamp(x, min, max):
    if x < min:
    return min
    elif x > max:
    return max
    return x


    # Temperature to RGB
    # OSL version:
    # http://blenderartists.org/forum/showthread.php?270332&p=2268693#post2268693
    def t2rgb(t):
    if t <= 6500:
    a = [0, -2902.1955373783176, -8257.7997278925690]
    @@ -113,13 +118,13 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):

    lumens_per_lamp = float(file_data[1])
    candela_mult = float(file_data[2])

    v_angles_num = int(file_data[3])
    h_angles_num = int(file_data[4])
    if not v_angles_num or not h_angles_num:
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    return {'CANCELED'}

    photometric_type = int(file_data[5])

    units_type = int(file_data[6])
    @@ -165,15 +170,16 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    y_data = [sum(x) / len(x) for x in zip(*candela_2d)]
    y_data_max = max(y_data)
    intensity = max(500, min(y_data_max * multiplier, 5000))
    lamp_data = list(zip(x_data, [0.5 + 0.5 * y / y_data_max for y in y_data]))

    lamp_data = list(zip(
    x_data, [0.5 + 0.5 * y / y_data_max for y in y_data]))

    return add_img(name=name,
    intensity=intensity,
    lamp_cone_type=lamp_cone_type,
    image_format=image_format,
    color_temperature=color_temperature,
    lamp_cone_type=lamp_cone_type,
    image_format=image_format,
    color_temperature=color_temperature,
    lamp_data=lamp_data)

    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))

    @@ -210,20 +216,20 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    intensity = max(500, min(maxval * multiplier, 5000))

    if image_format == 'PNG':
    float_buffer=False
    filepath='//' + name + '.png'
    float_buffer = False
    filepath = '//' + name + '.png'
    else:
    float_buffer=True
    filepath='//' + name + '.exr'
    float_buffer = True
    filepath = '//' + name + '.exr'

    img = bpy.data.images.new(name, len(v_angs) + 2, len(h_angs),
    float_buffer=float_buffer)

    for i in range(len(candela_values)):
    val = candela_mult * candela_values[i] / maxval
    img.pixels[4 * i] = img.pixels[4 * i + 1] = img.pixels[4 * i + 2] = val

    bpy.ops.import_lamp.gen_exr('INVOKE_DEFAULT',
    bpy.ops.import_lamp.gen_exr('INVOKE_DEFAULT',
    image_name=img.name,
    intensity=intensity,
    lamp_cone_type=lamp_cone_type,
    @@ -247,6 +253,7 @@ def scale_coords(nt, sock_in, sock_out, size):

    nt.links.new(sock_out, mul.outputs[0])


    def add_h_angles(nt, x, y, out):
    na = nt.nodes.new('MATH')
    na.operation = 'MULTIPLY'
    @@ -267,7 +274,7 @@ def add_h_angles(nt, x, y, out):
    nd.operation = 'POWER'
    nt.links.new(nd.inputs[0], nc.outputs[0])
    nd.inputs[1].default_value = 0.5

    nf = nt.nodes.new('MATH')
    nf.operation = 'ADD'
    nt.links.new(nf.inputs[0], x)
    @@ -281,7 +288,7 @@ def add_h_angles(nt, x, y, out):
    nh = nt.nodes.new('MATH')
    nh.operation = 'ARCTANGENT'
    nt.links.new(nh.inputs[0], ng.outputs[0])

    nj = nt.nodes.new('MATH')
    nj.operation = 'DIVIDE'
    nt.links.new(nj.inputs[0], nh.outputs[0])
    @@ -291,11 +298,12 @@ def add_h_angles(nt, x, y, out):
    nk.operation = 'ADD'
    nt.links.new(nk.inputs[0], nj.outputs[0])
    nk.inputs[1].default_value = 0.5

    nt.links.new(out, nk.outputs[0])


    def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, filepath=None, lamp_data=None):
    def add_img(name, intensity, lamp_cone_type, image_format, color_temperature,
    filepath=None, lamp_data=None):
    if image_format != 'VCURVES':
    img = bpy.data.images[name]
    img.filepath_raw = filepath
    @@ -317,23 +325,23 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, fi
    ni.inputs[1].default_value = pi
    else: # TYPE90:
    ni.inputs[1].default_value = pi / 2

    if image_format == 'VCURVES':
    nt_data = nt.nodes.new('CURVE_VEC')
    nt.links.new(nt_data.inputs[1], ni.outputs[0])
    for x, y in lamp_data[:-1]:
    pt = nt_data.mapping.curves[0].points.new(x, y)
    pt.handle_type = 'VECTOR'

    if lamp_cone_type == 'TYPE180':
    nt_data.mapping.curves[0].points[-1].location[1] = lamp_data[-1][1]
    nt_data.mapping.curves[0].points[-1].handle_type = 'VECTOR'
    else:
    pt = nt_data.mapping.curves[0].points.new(0.9999, lamp_data[-1][1])
    pt.handle_type = 'VECTOR'
    nt_data.mapping.curves[0].points[-1].location[1] = 0.5 # no light
    nt_data.mapping.curves[0].points[-1].location[1] = 0.5 # no light
    nt_data.mapping.curves[0].points[-1].handle_type = 'VECTOR'

    nt_data_sep = nt.nodes.new('SEPRGB')
    nt.links.new(nt_data_sep.inputs[0], nt_data.outputs[0])
    nt_data_out = nt_data_sep.outputs[0]
    @@ -347,7 +355,7 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, fi
    nt_data.color_space = 'NONE'
    nt.links.new(nt_data.inputs[0], n2.outputs[0])
    nt_data_out = nt_data.outputs[0]

    i1 = nt.inputs.new('Vector', 'VECTOR')
    i2 = nt.inputs.new('Strength', 'VALUE')
    nt.links.new(n0.inputs[0], i1)
    @@ -431,22 +439,23 @@ class ImportIES(Operator, ImportHelper):
    description="Multiplier for lamp strength",
    default=1.0,
    )

    image_format = EnumProperty(
    name='Convert to',
    items=format_prop_items,
    items=format_prop_items,
    default=format_prop_default,
    )

    color_temperature = IntProperty(
    name="Color Temperature",
    description="Color temperature of lamp, 3000=soft white, 5000=cool white, 6500=daylight",
    description="Color temperature of lamp, 3000=soft white, "
    "5000=cool white, 6500=daylight",
    default=6500,
    )

    def execute(self, context):
    return read_lamp_data(self.report, self.filepath, self.lamp_strength,
    self.image_format, self.color_temperature)
    return read_lamp_data(self.report, self.filepath, self.lamp_strength,
    self.image_format, self.color_temperature)


    class ExportLampEXR(Operator, ExportHelper):
    @@ -466,10 +475,10 @@ class ExportLampEXR(Operator, ExportHelper):
    use_filter_image = True

    def execute(self, context):
    return add_img(name=self.image_name,
    return add_img(name=self.image_name,
    intensity=self.intensity,
    lamp_cone_type=self.lamp_cone_type,
    image_format=self.image_format,
    lamp_cone_type=self.lamp_cone_type,
    image_format=self.image_format,
    color_temperature=self.color_temperature,
    filepath=self.filepath)

    @@ -499,6 +508,6 @@ def unregister():

    if __name__ == "__main__":
    register()

    # test call
    # bpy.ops.import_lamp.ies('INVOKE_DEFAULT')
  8. AngryLoki revised this gist Feb 23, 2013. 1 changed file with 3 additions and 5 deletions.
    8 changes: 3 additions & 5 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -21,7 +21,7 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 3),
    "version": (0, 4),
    "blender": (2, 6, 5),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    @@ -411,14 +411,12 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, fi
    from bpy.types import Operator

    format_prop_items = (
    ('VCURVES', "Vector Curves", "Save lamp data in Vector Curves node"),
    ('OPEN_EXR', "EXR", "Save images to EXR format (up to 5 textures)"),
    ('PNG', "PNG", "Save images to PNG format")
    )
    format_prop_default = 'PNG'

    if bpy.app.build_revision >= b'52886':
    format_prop_items += (('VCURVES', "Vector Curves", "Save lamp data in Vector Curves node"), )
    format_prop_default = 'VCURVES'
    format_prop_default = 'VCURVES'


    class ImportIES(Operator, ImportHelper):
  9. AngryLoki revised this gist Dec 27, 2012. 1 changed file with 19 additions and 17 deletions.
    36 changes: 19 additions & 17 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -1,20 +1,22 @@
    /*
    * Copyright 2012, Blender Foundation.
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    * as published by the Free Software Foundation; either version 2
    * of the License, or (at your option) any later version.
    *
    * This program is distributed in the hope that it will be useful,
    * but WITHOUT ANY WARRANTY; without even the implied warranty of
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    * GNU General Public License for more details.
    *
    * You should have received a copy of the GNU General Public License
    * along with this program; if not, write to the Free Software Foundation,
    * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    */
    # ##### BEGIN GPL LICENSE BLOCK #####
    #
    # This program is free software; you can redistribute it and/or
    # modify it under the terms of the GNU General Public License
    # as published by the Free Software Foundation; either version 2
    # of the License, or (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program; if not, write to the Free Software Foundation,
    # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####

    # <pep8 compliant>

    bl_info = {
    "name": "IES to Cycles",
  10. AngryLoki revised this gist Dec 27, 2012. 1 changed file with 18 additions and 0 deletions.
    18 changes: 18 additions & 0 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,21 @@
    /*
    * Copyright 2012, Blender Foundation.
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    * as published by the Free Software Foundation; either version 2
    * of the License, or (at your option) any later version.
    *
    * This program is distributed in the hope that it will be useful,
    * but WITHOUT ANY WARRANTY; without even the implied warranty of
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    * GNU General Public License for more details.
    *
    * You should have received a copy of the GNU General Public License
    * along with this program; if not, write to the Free Software Foundation,
    * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    */

    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
  11. AngryLoki revised this gist Dec 27, 2012. 1 changed file with 11 additions and 33 deletions.
    44 changes: 11 additions & 33 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -15,6 +15,7 @@
    import os

    from math import log, pow, pi
    from operator import add, truediv

    def clamp(x, min, max):
    if x < min:
    @@ -23,42 +24,19 @@ def clamp(x, min, max):
    return max
    return x

    def t2rgb(tmp):
    # Temperature must fall between 1000 and 40000 degrees
    tmp = clamp(tmp, 1000, 40000)
    def t2rgb(t):
    if t <= 6500:
    a = [0, -2902.1955373783176, -8257.7997278925690]
    b = [0, 1669.5803561666639, 2575.2827530017594]
    c = [1, 1.3302673723350029, 1.8993753891711275]

    # All calculations require tmp / 100, so only do the conversion once
    tmp /= 100

    # red
    if tmp <= 66:
    r = 255
    else:
    # Note: the R-squared value for this approximation is .988
    r = 329.698727446 * pow(tmp - 60, -0.1332047592)
    r = clamp(r, 0, 255)

    # green
    if tmp <= 66:
    # Note: the R-squared value for this approximation is .996
    g = 99.4708025861 * log(tmp) - 161.1195681661
    g = clamp(g, 0, 255)
    else:
    # Note: the R-squared value for this approximation is .987
    g = 288.1221695283 * pow(tmp - 60, -0.0755148492)
    g = clamp(g, 0, 255)

    # blue
    if tmp >= 66:
    b = 255
    elif tmp <= 19:
    b = 0
    else:
    # Note: the R-squared value for this approximation is .998
    b = 138.5177312231 * log(tmp - 10) - 305.0447927307
    b = clamp(b, 0, 255)
    a = [1745.0425298314172, 1216.6168361476490, -8257.7997278925690]
    b = [-2666.3474220535695, -2173.1012343082230, 2575.2827530017594]
    c = [0.55995389139931482, 0.70381203140554553, 1.8993753891711275]

    return [r/255.0, g/255.0, b/255.0, 1.0]
    color = map(add, map(truediv, a, map(add, [t] * 3, b)), c)
    return [max(0, min(x, 1)) for x in color] + [1]


    def simple_interp(k, x, y):
  12. AngryLoki revised this gist Dec 26, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -418,7 +418,7 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, fi
    )
    format_prop_default = 'PNG'

    if int(bpy.app.build_revision) > 52886:
    if bpy.app.build_revision >= b'52886':
    format_prop_items += (('VCURVES', "Vector Curves", "Save lamp data in Vector Curves node"), )
    format_prop_default = 'VCURVES'

  13. AngryLoki revised this gist Dec 24, 2012. 1 changed file with 7 additions and 3 deletions.
    10 changes: 7 additions & 3 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -412,12 +412,16 @@ def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, fi
    from bpy.props import StringProperty, FloatProperty, EnumProperty, IntProperty
    from bpy.types import Operator


    format_prop_items = (
    ('VCURVES', "Vector Curves", "Save lamp data in Vector Curves node"),
    ('OPEN_EXR', "EXR", "Save images to EXR format (up to 5 textures)"),
    ('PNG', "PNG", "Save images to PNG format")
    )
    format_prop_default = 'PNG'

    if int(bpy.app.build_revision) > 52886:
    format_prop_items += (('VCURVES', "Vector Curves", "Save lamp data in Vector Curves node"), )
    format_prop_default = 'VCURVES'


    class ImportIES(Operator, ImportHelper):
    """Import IES lamp data and generate a node group for cycles"""
    @@ -435,7 +439,7 @@ class ImportIES(Operator, ImportHelper):
    image_format = EnumProperty(
    name='Convert to',
    items=format_prop_items,
    default='VCURVES',
    default=format_prop_default,
    )

    color_temperature = IntProperty(
  14. AngryLoki revised this gist Dec 24, 2012. 1 changed file with 112 additions and 79 deletions.
    191 changes: 112 additions & 79 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 2),
    "version": (0, 3),
    "blender": (2, 6, 5),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    @@ -14,7 +14,7 @@
    import bpy
    import os

    from math import log, pow
    from math import log, pow, pi

    def clamp(x, min, max):
    if x < min:
    @@ -101,21 +101,12 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    if endbracket != -1:
    keywords[s[1:endbracket]] = s[endbracket + 1:].strip()

    #required_kwds = ['TEST', 'TESTLAB', 'ISSUEDATE', 'MANUFAC']
    #if version == 2002:
    # for kwd in required_kwds:
    # if kwd not in keywords:
    # print('Required keyword %s is missing' % kwd)

    s, content = content.split('\n', 1)

    if not s.startswith('TILT'):
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    return {'CANCELED'}

    #if s != 'TILT=NONE':
    # print('' % kwd)

    file_data = content.replace(',', ' ').split()

    lamps_num = int(file_data[0])
    @@ -124,8 +115,13 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):

    lumens_per_lamp = float(file_data[1])
    candela_mult = float(file_data[2])

    v_angles_num = int(file_data[3])
    h_angles_num = int(file_data[4])
    if not v_angles_num or not h_angles_num:
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    return {'CANCELED'}

    photometric_type = int(file_data[5])

    units_type = int(file_data[6])
    @@ -157,21 +153,39 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    (v_angs[0], v_angs[-1]))
    lamp_cone_type = 'TYPE180'

    # check if angular offsets are the same
    v_d = [v_angs[i] - v_angs[i - 1] for i in range(1, len(v_angs))]
    h_d = [h_angs[i] - h_angs[i - 1] for i in range(1, len(h_angs))]

    v_same = all(abs(v_d[i] - v_d[i - 1]) < 0.001 for i in range(1, len(v_d)))
    h_same = all(abs(h_d[i] - h_d[i - 1]) < 0.001 for i in range(1, len(h_d)))

    # read candela values
    offset = 13 + len(v_angs) + len(h_angs)
    candela_num = len(v_angs) * len(h_angs)
    candela_values = [float(s) for s in file_data[offset:offset + candela_num]]

    if image_format == 'VCURVES':
    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))
    # scale vertical angles to [0, 1] range
    x_data = [0.5 + 0.5 * x / v_angs[-1] for x in v_angs]
    # approximate multidimentional lamp data to single dimention
    y_data = [sum(x) / len(x) for x in zip(*candela_2d)]
    y_data_max = max(y_data)
    intensity = max(500, min(y_data_max * multiplier, 5000))
    lamp_data = list(zip(x_data, [0.5 + 0.5 * y / y_data_max for y in y_data]))

    return add_img(name=name,
    intensity=intensity,
    lamp_cone_type=lamp_cone_type,
    image_format=image_format,
    color_temperature=color_temperature,
    lamp_data=lamp_data)

    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))

    # check if angular offsets are the same
    v_d = [v_angs[i] - v_angs[i - 1] for i in range(1, len(v_angs))]
    h_d = [h_angs[i] - h_angs[i - 1] for i in range(1, len(h_angs))]

    v_same = all(abs(v_d[i] - v_d[i - 1]) < 0.001 for i in range(1, len(v_d)))
    h_same = all(abs(h_d[i] - h_d[i - 1]) < 0.001 for i in range(1, len(h_d)))

    if not v_same:
    vmin, vmax = v_angs[0], v_angs[-1]
    divisions = int((vmax - vmin) / max(1, min(v_d)))
    @@ -193,7 +207,9 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):

    # flatten 2d array to 1d
    candela_values = [y for x in candela_2d for y in x]

    maxval = max(candela_values)
    intensity = max(500, min(maxval * multiplier, 5000))

    if image_format == 'PNG':
    float_buffer=False
    @@ -209,13 +225,13 @@ def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    val = candela_mult * candela_values[i] / maxval
    img.pixels[4 * i] = img.pixels[4 * i + 1] = img.pixels[4 * i + 2] = val

    intensity = max(500, min(maxval * multiplier, 5000))
    bpy.ops.import_lamp.gen_exr('INVOKE_DEFAULT', image_name=img.name,
    bpy.ops.import_lamp.gen_exr('INVOKE_DEFAULT',
    image_name=img.name,
    intensity=intensity,
    filepath=filepath,
    lamp_cone_type=lamp_cone_type,
    image_format=image_format,
    color_temperature=color_temperature)
    color_temperature=color_temperature,
    filepath=filepath)

    return {'FINISHED'}

    @@ -280,71 +296,87 @@ def add_h_angles(nt, x, y, out):

    nt.links.new(out, nk.outputs[0])

    def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color_temperature):
    from math import pi

    img = bpy.data.images[image_name]
    img.filepath_raw = filepath
    img.file_format = image_format
    img.save()
    def add_img(name, intensity, lamp_cone_type, image_format, color_temperature, filepath=None, lamp_data=None):
    if image_format != 'VCURVES':
    img = bpy.data.images[name]
    img.filepath_raw = filepath
    img.file_format = image_format
    img.save()

    if 0 and 'lamp_routine' in bpy.data.node_groups:
    nt = bpy.data.node_groups['lamp_routine']
    else:
    nt = bpy.data.node_groups.new('Lamp ' + image_name, 'SHADER')
    n0 = nt.nodes.new('SEPRGB')
    nt = bpy.data.node_groups.new('Lamp ' + name, 'SHADER')
    n0 = nt.nodes.new('SEPRGB')

    ne = nt.nodes.new('MATH')
    ne.operation = 'ARCCOSINE'
    nt.links.new(ne.inputs[0], n0.outputs[2])
    ne = nt.nodes.new('MATH')
    ne.operation = 'ARCCOSINE'
    nt.links.new(ne.inputs[0], n0.outputs[2])

    ni = nt.nodes.new('MATH')
    ni.operation = 'DIVIDE'
    nt.links.new(ni.inputs[0], ne.outputs[0])
    ni = nt.nodes.new('MATH')
    ni.operation = 'DIVIDE'
    nt.links.new(ni.inputs[0], ne.outputs[0])

    if lamp_cone_type == 'TYPE180':
    ni.inputs[1].default_value = pi
    else: # TYPE90:
    ni.inputs[1].default_value = pi / 2

    if image_format == 'VCURVES':
    nt_data = nt.nodes.new('CURVE_VEC')
    nt.links.new(nt_data.inputs[1], ni.outputs[0])
    for x, y in lamp_data[:-1]:
    pt = nt_data.mapping.curves[0].points.new(x, y)
    pt.handle_type = 'VECTOR'

    if lamp_cone_type == 'TYPE180':
    ni.inputs[1].default_value = pi
    else: # TYPE90:
    ni.inputs[1].default_value = pi / 2

    nt_data.mapping.curves[0].points[-1].location[1] = lamp_data[-1][1]
    nt_data.mapping.curves[0].points[-1].handle_type = 'VECTOR'
    else:
    pt = nt_data.mapping.curves[0].points.new(0.9999, lamp_data[-1][1])
    pt.handle_type = 'VECTOR'
    nt_data.mapping.curves[0].points[-1].location[1] = 0.5 # no light
    nt_data.mapping.curves[0].points[-1].handle_type = 'VECTOR'

    nt_data_sep = nt.nodes.new('SEPRGB')
    nt.links.new(nt_data_sep.inputs[0], nt_data.outputs[0])
    nt_data_out = nt_data_sep.outputs[0]
    else:
    n2 = nt.nodes.new('COMBRGB')
    scale_coords(nt, ni.outputs[0], n2.inputs[0], img.size[0])

    if img.size[1] > 1:
    add_h_angles(nt, n0.outputs[0], n0.outputs[1], n2.inputs[1])
    nt_data = nt.nodes.new('TEX_IMAGE')
    nt_data.image = img
    nt_data.color_space = 'NONE'
    nt.links.new(nt_data.inputs[0], n2.outputs[0])
    nt_data_out = nt_data.outputs[0]

    i1 = nt.inputs.new('Vector', 'VECTOR')
    i2 = nt.inputs.new('Strength', 'VALUE')
    nt.links.new(n0.inputs[0], i1)

    nt_ima = nt.nodes.new('TEX_IMAGE')
    nt_ima.image = img
    nt_ima.color_space = 'NONE'
    nt.links.new(nt_ima.inputs[0], n2.outputs[0])

    i1 = nt.inputs.new('Vector', 'VECTOR')
    i2 = nt.inputs.new('Strength', 'VALUE')
    nt.links.new(n0.inputs[0], i1)

    nmult = nt.nodes.new('MATH')
    nmult.operation = 'MULTIPLY'
    nt.links.new(nmult.inputs[0], i2)
    nmult = nt.nodes.new('MATH')
    nmult.operation = 'MULTIPLY'
    nt.links.new(nmult.inputs[0], i2)

    o1 = nt.outputs.new('Intensity', 'VALUE')
    nt.links.new(o1, nmult.outputs[0])
    o1 = nt.outputs.new('Intensity', 'VALUE')
    nt.links.new(o1, nmult.outputs[0])

    if lamp_cone_type == 'TYPE180':
    nt.links.new(nmult.inputs[1], nt_ima.outputs[0])
    else: # TYPE90
    nlt = nt.nodes.new('MATH')
    nlt.operation = 'LESS_THAN'
    nt.links.new(nlt.inputs[0], ni.outputs[0])
    nlt.inputs[1].default_value = 1.0
    if lamp_cone_type == 'TYPE180' or image_format == 'VCURVES':
    nt.links.new(nmult.inputs[1], nt_data_out)
    else: # TYPE90
    nlt = nt.nodes.new('MATH')
    nlt.operation = 'LESS_THAN'
    nt.links.new(nlt.inputs[0], ni.outputs[0])
    nlt.inputs[1].default_value = 1.0

    nif = nt.nodes.new('MATH')
    nif.operation = 'MULTIPLY'
    nt.links.new(nif.inputs[0], nt_ima.outputs[0])
    nt.links.new(nif.inputs[1], nlt.outputs[0])
    nif = nt.nodes.new('MATH')
    nif.operation = 'MULTIPLY'
    nt.links.new(nif.inputs[0], nt_data_out)
    nt.links.new(nif.inputs[1], nlt.outputs[0])

    nt.links.new(nmult.inputs[1], nif.outputs[0])
    nt.links.new(nmult.inputs[1], nif.outputs[0])

    lampdata = bpy.data.lamps.new('Lamp ' + image_name, 'POINT')
    lampdata = bpy.data.lamps.new('Lamp ' + name, 'POINT')
    lampdata.use_nodes = True
    lnt = lampdata.node_tree

    @@ -363,7 +395,7 @@ def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color
    lnt_geo = lnt.nodes.new('NEW_GEOMETRY')
    lnt.links.new(lnt_map.inputs[0], lnt_geo.outputs[1])

    lamp = bpy.data.objects.new('Lamp ' + image_name, lampdata)
    lamp = bpy.data.objects.new('Lamp ' + name, lampdata)
    lamp.location = bpy.context.scene.cursor_location
    bpy.context.scene.objects.link(lamp)

    @@ -382,6 +414,7 @@ def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color


    format_prop_items = (
    ('VCURVES', "Vector Curves", "Save lamp data in Vector Curves node"),
    ('OPEN_EXR', "EXR", "Save images to EXR format (up to 5 textures)"),
    ('PNG', "PNG", "Save images to PNG format")
    )
    @@ -402,7 +435,7 @@ class ImportIES(Operator, ImportHelper):
    image_format = EnumProperty(
    name='Convert to',
    items=format_prop_items,
    default='PNG',
    default='VCURVES',
    )

    color_temperature = IntProperty(
    @@ -433,18 +466,18 @@ class ExportLampEXR(Operator, ExportHelper):
    use_filter_image = True

    def execute(self, context):
    return add_img(self.image_name, self.filepath, self.intensity,
    self.lamp_cone_type, self.image_format, self.color_temperature)
    # self.report({'ERROR'}, "Could not make new image %s at %s" %
    # (self.image_name, self.filepath))
    return add_img(name=self.image_name,
    intensity=self.intensity,
    lamp_cone_type=self.lamp_cone_type,
    image_format=self.image_format,
    color_temperature=self.color_temperature,
    filepath=self.filepath)

    def invoke(self, context, event):
    if self.image_format == 'PNG':
    self.filename_ext = ".png"
    # self.filter_glob = StringProperty(default="*.png", options={'HIDDEN'})
    else:
    self.filename_ext = ".exr"
    # self.filter_glob = StringProperty(default="*.exr", options={'HIDDEN'})

    return ExportHelper.invoke(self, context, event)

  15. AngryLoki revised this gist Dec 24, 2012. 1 changed file with 50 additions and 46 deletions.
    96 changes: 50 additions & 46 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 1),
    "version": (0, 2),
    "blender": (2, 6, 5),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    @@ -233,6 +233,52 @@ def scale_coords(nt, sock_in, sock_out, size):

    nt.links.new(sock_out, mul.outputs[0])

    def add_h_angles(nt, x, y, out):
    na = nt.nodes.new('MATH')
    na.operation = 'MULTIPLY'
    nt.links.new(na.inputs[0], x)
    nt.links.new(na.inputs[1], x)

    nb = nt.nodes.new('MATH')
    nb.operation = 'MULTIPLY'
    nt.links.new(nb.inputs[0], y)
    nt.links.new(nb.inputs[1], y)

    nc = nt.nodes.new('MATH')
    nc.operation = 'ADD'
    nt.links.new(nc.inputs[0], na.outputs[0])
    nt.links.new(nc.inputs[1], nb.outputs[0])

    nd = nt.nodes.new('MATH')
    nd.operation = 'POWER'
    nt.links.new(nd.inputs[0], nc.outputs[0])
    nd.inputs[1].default_value = 0.5

    nf = nt.nodes.new('MATH')
    nf.operation = 'ADD'
    nt.links.new(nf.inputs[0], x)
    nt.links.new(nf.inputs[1], nd.outputs[0])

    ng = nt.nodes.new('MATH')
    ng.operation = 'DIVIDE'
    nt.links.new(ng.inputs[0], y)
    nt.links.new(ng.inputs[1], nf.outputs[0])

    nh = nt.nodes.new('MATH')
    nh.operation = 'ARCTANGENT'
    nt.links.new(nh.inputs[0], ng.outputs[0])

    nj = nt.nodes.new('MATH')
    nj.operation = 'DIVIDE'
    nt.links.new(nj.inputs[0], nh.outputs[0])
    nj.inputs[1].default_value = pi

    nk = nt.nodes.new('MATH')
    nk.operation = 'ADD'
    nt.links.new(nk.inputs[0], nj.outputs[0])
    nk.inputs[1].default_value = 0.5

    nt.links.new(out, nk.outputs[0])

    def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color_temperature):
    from math import pi
    @@ -248,44 +294,10 @@ def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color
    nt = bpy.data.node_groups.new('Lamp ' + image_name, 'SHADER')
    n0 = nt.nodes.new('SEPRGB')

    na = nt.nodes.new('MATH')
    na.operation = 'MULTIPLY'
    nt.links.new(na.inputs[0], n0.outputs[0])
    nt.links.new(na.inputs[1], n0.outputs[0])

    nb = nt.nodes.new('MATH')
    nb.operation = 'MULTIPLY'
    nt.links.new(nb.inputs[0], n0.outputs[1])
    nt.links.new(nb.inputs[1], n0.outputs[1])

    nc = nt.nodes.new('MATH')
    nc.operation = 'ADD'
    nt.links.new(nc.inputs[0], na.outputs[0])
    nt.links.new(nc.inputs[1], nb.outputs[0])

    nd = nt.nodes.new('MATH')
    nd.operation = 'POWER'
    nt.links.new(nd.inputs[0], nc.outputs[0])
    nd.inputs[1].default_value = 0.5

    ne = nt.nodes.new('MATH')
    ne.operation = 'ARCCOSINE'
    nt.links.new(ne.inputs[0], n0.outputs[2])

    nf = nt.nodes.new('MATH')
    nf.operation = 'ADD'
    nt.links.new(nf.inputs[0], n0.outputs[0])
    nt.links.new(nf.inputs[1], nd.outputs[0])

    ng = nt.nodes.new('MATH')
    ng.operation = 'DIVIDE'
    nt.links.new(ng.inputs[0], n0.outputs[1])
    nt.links.new(ng.inputs[1], nf.outputs[0])

    nh = nt.nodes.new('MATH')
    nh.operation = 'ARCTANGENT'
    nt.links.new(nh.inputs[0], ng.outputs[0])

    ni = nt.nodes.new('MATH')
    ni.operation = 'DIVIDE'
    nt.links.new(ni.inputs[0], ne.outputs[0])
    @@ -295,19 +307,11 @@ def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color
    else: # TYPE90:
    ni.inputs[1].default_value = pi / 2

    nj = nt.nodes.new('MATH')
    nj.operation = 'DIVIDE'
    nt.links.new(nj.inputs[0], nh.outputs[0])
    nj.inputs[1].default_value = pi

    nk = nt.nodes.new('MATH')
    nk.operation = 'ADD'
    nt.links.new(nk.inputs[0], nj.outputs[0])
    nk.inputs[1].default_value = 0.5

    n2 = nt.nodes.new('COMBRGB')
    scale_coords(nt, ni.outputs[0], n2.inputs[0], img.size[0])
    nt.links.new(n2.inputs[1], nk.outputs[0])

    if img.size[1] > 1:
    add_h_angles(nt, n0.outputs[0], n0.outputs[1], n2.inputs[1])

    nt_ima = nt.nodes.new('TEX_IMAGE')
    nt_ima.image = img
  16. AngryLoki revised this gist Dec 23, 2012. 1 changed file with 61 additions and 6 deletions.
    67 changes: 61 additions & 6 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,52 @@
    import bpy
    import os

    from math import log, pow

    def clamp(x, min, max):
    if x < min:
    return min
    elif x > max:
    return max
    return x

    def t2rgb(tmp):
    # Temperature must fall between 1000 and 40000 degrees
    tmp = clamp(tmp, 1000, 40000)

    # All calculations require tmp / 100, so only do the conversion once
    tmp /= 100

    # red
    if tmp <= 66:
    r = 255
    else:
    # Note: the R-squared value for this approximation is .988
    r = 329.698727446 * pow(tmp - 60, -0.1332047592)
    r = clamp(r, 0, 255)

    # green
    if tmp <= 66:
    # Note: the R-squared value for this approximation is .996
    g = 99.4708025861 * log(tmp) - 161.1195681661
    g = clamp(g, 0, 255)
    else:
    # Note: the R-squared value for this approximation is .987
    g = 288.1221695283 * pow(tmp - 60, -0.0755148492)
    g = clamp(g, 0, 255)

    # blue
    if tmp >= 66:
    b = 255
    elif tmp <= 19:
    b = 0
    else:
    # Note: the R-squared value for this approximation is .998
    b = 138.5177312231 * log(tmp - 10) - 305.0447927307
    b = clamp(b, 0, 255)

    return [r/255.0, g/255.0, b/255.0, 1.0]


    def simple_interp(k, x, y):
    for i in range(len(x)):
    @@ -23,7 +69,7 @@ def simple_interp(k, x, y):
    return y[i] + (k - x[i]) * (y[i - 1] - y[i]) / (x[i - 1] - x[i])


    def read_lamp_data(log, filename, multiplier, image_format):
    def read_lamp_data(log, filename, multiplier, image_format, color_temperature):
    version_table = {
    'IESNA:LM-63-1986': 1986,
    'IESNA:LM-63-1991': 1991,
    @@ -168,7 +214,8 @@ def read_lamp_data(log, filename, multiplier, image_format):
    intensity=intensity,
    filepath=filepath,
    lamp_cone_type=lamp_cone_type,
    image_format=image_format)
    image_format=image_format,
    color_temperature=color_temperature)

    return {'FINISHED'}

    @@ -187,7 +234,7 @@ def scale_coords(nt, sock_in, sock_out, size):
    nt.links.new(sock_out, mul.outputs[0])


    def add_img(image_name, filepath, intensity, lamp_cone_type, image_format):
    def add_img(image_name, filepath, intensity, lamp_cone_type, image_format, color_temperature):
    from math import pi

    img = bpy.data.images[image_name]
    @@ -301,6 +348,7 @@ def add_img(image_name, filepath, intensity, lamp_cone_type, image_format):
    # print(node)

    lnt_grp = lnt.nodes.new('GROUP', group=nt)
    lnt.nodes['Emission'].inputs[0].default_value = t2rgb(color_temperature)
    lnt.links.new(lnt.nodes['Emission'].inputs[1], lnt_grp.outputs[0])
    lnt_grp.inputs[1].default_value = intensity

    @@ -325,7 +373,7 @@ def add_img(image_name, filepath, intensity, lamp_cone_type, image_format):


    from bpy_extras.io_utils import ImportHelper, ExportHelper
    from bpy.props import StringProperty, FloatProperty, EnumProperty
    from bpy.props import StringProperty, FloatProperty, EnumProperty, IntProperty
    from bpy.types import Operator


    @@ -353,9 +401,15 @@ class ImportIES(Operator, ImportHelper):
    default='PNG',
    )

    color_temperature = IntProperty(
    name="Color Temperature",
    description="Color temperature of lamp, 3000=soft white, 5000=cool white, 6500=daylight",
    default=6500,
    )

    def execute(self, context):
    return read_lamp_data(self.report, self.filepath, self.lamp_strength,
    self.image_format)
    self.image_format, self.color_temperature)


    class ExportLampEXR(Operator, ExportHelper):
    @@ -371,11 +425,12 @@ class ExportLampEXR(Operator, ExportHelper):
    options={'HIDDEN'}
    )
    image_format = EnumProperty(items=format_prop_items, options={'HIDDEN'})
    color_temperature = IntProperty(options={'HIDDEN'})
    use_filter_image = True

    def execute(self, context):
    return add_img(self.image_name, self.filepath, self.intensity,
    self.lamp_cone_type, self.image_format)
    self.lamp_cone_type, self.image_format, self.color_temperature)
    # self.report({'ERROR'}, "Could not make new image %s at %s" %
    # (self.image_name, self.filepath))

  17. AngryLoki revised this gist Dec 23, 2012. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -408,4 +408,5 @@ def unregister():
    if __name__ == "__main__":
    register()

    bpy.ops.import_lamp.ies('INVOKE_DEFAULT')
    # test call
    # bpy.ops.import_lamp.ies('INVOKE_DEFAULT')
  18. AngryLoki created this gist Dec 23, 2012.
    411 changes: 411 additions & 0 deletions ies2cycles.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,411 @@
    bl_info = {
    "name": "IES to Cycles",
    "author": "Lockal S.",
    "version": (0, 1),
    "blender": (2, 6, 5),
    "location": "File > Import > IES Lamp Data (.ies)",
    "description": "Import IES lamp data to cycles",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Import-Export"
    }

    import bpy
    import os


    def simple_interp(k, x, y):
    for i in range(len(x)):
    if k == x[i]:
    return y[i]
    elif k < x[i]:
    return y[i] + (k - x[i]) * (y[i - 1] - y[i]) / (x[i - 1] - x[i])


    def read_lamp_data(log, filename, multiplier, image_format):
    version_table = {
    'IESNA:LM-63-1986': 1986,
    'IESNA:LM-63-1991': 1991,
    'IESNA91': 1991,
    'IESNA:LM-63-1995': 1995,
    'IESNA:LM-63-2002': 2002,
    }

    name = os.path.splitext(os.path.split(filename)[1])[0]

    file = open(filename, 'rt', encoding='cp1252')
    content = file.read()
    file.close()
    s, content = content.split('\n', 1)

    if s in version_table:
    version = version_table[s]
    else:
    log({'DEBUG'}, 'IES file does not specify any version')
    version = None

    keywords = dict()

    while content and not content.startswith('TILT='):
    s, content = content.split('\n', 1)

    if s.startswith('['):
    endbracket = s.find(']')
    if endbracket != -1:
    keywords[s[1:endbracket]] = s[endbracket + 1:].strip()

    #required_kwds = ['TEST', 'TESTLAB', 'ISSUEDATE', 'MANUFAC']
    #if version == 2002:
    # for kwd in required_kwds:
    # if kwd not in keywords:
    # print('Required keyword %s is missing' % kwd)

    s, content = content.split('\n', 1)

    if not s.startswith('TILT'):
    log({'ERROR'}, 'TILT keyword not found, check your IES file')
    return {'CANCELED'}

    #if s != 'TILT=NONE':
    # print('' % kwd)

    file_data = content.replace(',', ' ').split()

    lamps_num = int(file_data[0])
    #if lamps_num != 1.0:
    # print('Only 1 lamp is supported at this moment')

    lumens_per_lamp = float(file_data[1])
    candela_mult = float(file_data[2])
    v_angles_num = int(file_data[3])
    h_angles_num = int(file_data[4])
    photometric_type = int(file_data[5])

    units_type = int(file_data[6])
    #if units_type not in [1, 2]:
    # print('Units type should be either 1 (feet) or 2 (meters)')

    width = float(file_data[7])
    length = float(file_data[8])
    height = float(file_data[9])

    ballast_factor = float(file_data[10])

    future_use = float(file_data[11])
    if future_use != 1.0:
    print('Invalid future use field')

    input_watts = float(file_data[12])

    v_angs = [float(s) for s in file_data[13:13 + v_angles_num]]
    h_angs = [float(s) for s in file_data[13 + v_angles_num:
    13 + v_angles_num + h_angles_num]]

    if v_angs[0] == 0 and v_angs[-1] == 90:
    lamp_cone_type = 'TYPE90'
    elif v_angs[0] == 0 and v_angs[-1] == 180:
    lamp_cone_type = 'TYPE180'
    else:
    log({'DEBUG'}, 'Lamps with vertical angles (%d-%d) are not supported' %
    (v_angs[0], v_angs[-1]))
    lamp_cone_type = 'TYPE180'

    # check if angular offsets are the same
    v_d = [v_angs[i] - v_angs[i - 1] for i in range(1, len(v_angs))]
    h_d = [h_angs[i] - h_angs[i - 1] for i in range(1, len(h_angs))]

    v_same = all(abs(v_d[i] - v_d[i - 1]) < 0.001 for i in range(1, len(v_d)))
    h_same = all(abs(h_d[i] - h_d[i - 1]) < 0.001 for i in range(1, len(h_d)))

    # read candela values
    offset = 13 + len(v_angs) + len(h_angs)
    candela_num = len(v_angs) * len(h_angs)
    candela_values = [float(s) for s in file_data[offset:offset + candela_num]]

    # reshape 1d array to 2d array
    candela_2d = list(zip(*[iter(candela_values)] * len(v_angs)))

    if not v_same:
    vmin, vmax = v_angs[0], v_angs[-1]
    divisions = int((vmax - vmin) / max(1, min(v_d)))
    step = (vmax - vmin) / divisions

    # Approximating non-uniform vertical angles with step = step
    new_v_angs = [vmin + i * step for i in range(divisions + 1)]
    new_candela_2d = [[simple_interp(ang, v_angs, line)
    for ang in new_v_angs] for line in candela_2d]
    # print(candela_2d)
    # print(new_candela_2d)
    v_angs = new_v_angs
    candela_2d = new_candela_2d

    if not h_same:
    log({'DEBUG'}, 'Different offsets for horizontal angles!')

    candela_2d = [[line[0]] + list(line) + [line[-1]] for line in candela_2d]

    # flatten 2d array to 1d
    candela_values = [y for x in candela_2d for y in x]
    maxval = max(candela_values)

    if image_format == 'PNG':
    float_buffer=False
    filepath='//' + name + '.png'
    else:
    float_buffer=True
    filepath='//' + name + '.exr'

    img = bpy.data.images.new(name, len(v_angs) + 2, len(h_angs),
    float_buffer=float_buffer)

    for i in range(len(candela_values)):
    val = candela_mult * candela_values[i] / maxval
    img.pixels[4 * i] = img.pixels[4 * i + 1] = img.pixels[4 * i + 2] = val

    intensity = max(500, min(maxval * multiplier, 5000))
    bpy.ops.import_lamp.gen_exr('INVOKE_DEFAULT', image_name=img.name,
    intensity=intensity,
    filepath=filepath,
    lamp_cone_type=lamp_cone_type,
    image_format=image_format)

    return {'FINISHED'}


    def scale_coords(nt, sock_in, sock_out, size):
    add = nt.nodes.new('MATH')
    add.operation = 'ADD'
    nt.links.new(add.inputs[0], sock_in)
    add.inputs[1].default_value = 1.0 / (size - 2)

    mul = nt.nodes.new('MATH')
    mul.operation = 'MULTIPLY'
    nt.links.new(mul.inputs[0], add.outputs[0])
    mul.inputs[1].default_value = (size - 2.0) / size

    nt.links.new(sock_out, mul.outputs[0])


    def add_img(image_name, filepath, intensity, lamp_cone_type, image_format):
    from math import pi

    img = bpy.data.images[image_name]
    img.filepath_raw = filepath
    img.file_format = image_format
    img.save()

    if 0 and 'lamp_routine' in bpy.data.node_groups:
    nt = bpy.data.node_groups['lamp_routine']
    else:
    nt = bpy.data.node_groups.new('Lamp ' + image_name, 'SHADER')
    n0 = nt.nodes.new('SEPRGB')

    na = nt.nodes.new('MATH')
    na.operation = 'MULTIPLY'
    nt.links.new(na.inputs[0], n0.outputs[0])
    nt.links.new(na.inputs[1], n0.outputs[0])

    nb = nt.nodes.new('MATH')
    nb.operation = 'MULTIPLY'
    nt.links.new(nb.inputs[0], n0.outputs[1])
    nt.links.new(nb.inputs[1], n0.outputs[1])

    nc = nt.nodes.new('MATH')
    nc.operation = 'ADD'
    nt.links.new(nc.inputs[0], na.outputs[0])
    nt.links.new(nc.inputs[1], nb.outputs[0])

    nd = nt.nodes.new('MATH')
    nd.operation = 'POWER'
    nt.links.new(nd.inputs[0], nc.outputs[0])
    nd.inputs[1].default_value = 0.5

    ne = nt.nodes.new('MATH')
    ne.operation = 'ARCCOSINE'
    nt.links.new(ne.inputs[0], n0.outputs[2])

    nf = nt.nodes.new('MATH')
    nf.operation = 'ADD'
    nt.links.new(nf.inputs[0], n0.outputs[0])
    nt.links.new(nf.inputs[1], nd.outputs[0])

    ng = nt.nodes.new('MATH')
    ng.operation = 'DIVIDE'
    nt.links.new(ng.inputs[0], n0.outputs[1])
    nt.links.new(ng.inputs[1], nf.outputs[0])

    nh = nt.nodes.new('MATH')
    nh.operation = 'ARCTANGENT'
    nt.links.new(nh.inputs[0], ng.outputs[0])

    ni = nt.nodes.new('MATH')
    ni.operation = 'DIVIDE'
    nt.links.new(ni.inputs[0], ne.outputs[0])

    if lamp_cone_type == 'TYPE180':
    ni.inputs[1].default_value = pi
    else: # TYPE90:
    ni.inputs[1].default_value = pi / 2

    nj = nt.nodes.new('MATH')
    nj.operation = 'DIVIDE'
    nt.links.new(nj.inputs[0], nh.outputs[0])
    nj.inputs[1].default_value = pi

    nk = nt.nodes.new('MATH')
    nk.operation = 'ADD'
    nt.links.new(nk.inputs[0], nj.outputs[0])
    nk.inputs[1].default_value = 0.5

    n2 = nt.nodes.new('COMBRGB')
    scale_coords(nt, ni.outputs[0], n2.inputs[0], img.size[0])
    nt.links.new(n2.inputs[1], nk.outputs[0])

    nt_ima = nt.nodes.new('TEX_IMAGE')
    nt_ima.image = img
    nt_ima.color_space = 'NONE'
    nt.links.new(nt_ima.inputs[0], n2.outputs[0])

    i1 = nt.inputs.new('Vector', 'VECTOR')
    i2 = nt.inputs.new('Strength', 'VALUE')
    nt.links.new(n0.inputs[0], i1)

    nmult = nt.nodes.new('MATH')
    nmult.operation = 'MULTIPLY'
    nt.links.new(nmult.inputs[0], i2)

    o1 = nt.outputs.new('Intensity', 'VALUE')
    nt.links.new(o1, nmult.outputs[0])

    if lamp_cone_type == 'TYPE180':
    nt.links.new(nmult.inputs[1], nt_ima.outputs[0])
    else: # TYPE90
    nlt = nt.nodes.new('MATH')
    nlt.operation = 'LESS_THAN'
    nt.links.new(nlt.inputs[0], ni.outputs[0])
    nlt.inputs[1].default_value = 1.0

    nif = nt.nodes.new('MATH')
    nif.operation = 'MULTIPLY'
    nt.links.new(nif.inputs[0], nt_ima.outputs[0])
    nt.links.new(nif.inputs[1], nlt.outputs[0])

    nt.links.new(nmult.inputs[1], nif.outputs[0])

    lampdata = bpy.data.lamps.new('Lamp ' + image_name, 'POINT')
    lampdata.use_nodes = True
    lnt = lampdata.node_tree

    #for node in lnt.nodes:
    # print(node)

    lnt_grp = lnt.nodes.new('GROUP', group=nt)
    lnt.links.new(lnt.nodes['Emission'].inputs[1], lnt_grp.outputs[0])
    lnt_grp.inputs[1].default_value = intensity

    lnt_map = lnt.nodes.new('MAPPING')
    lnt_map.rotation[0] = pi
    lnt.links.new(lnt_grp.inputs[0], lnt_map.outputs[0])

    lnt_geo = lnt.nodes.new('NEW_GEOMETRY')
    lnt.links.new(lnt_map.inputs[0], lnt_geo.outputs[1])

    lamp = bpy.data.objects.new('Lamp ' + image_name, lampdata)
    lamp.location = bpy.context.scene.cursor_location
    bpy.context.scene.objects.link(lamp)

    for ob in bpy.data.objects:
    ob.select = False

    lamp.select = True
    bpy.context.scene.objects.active = lamp

    return {'FINISHED'}


    from bpy_extras.io_utils import ImportHelper, ExportHelper
    from bpy.props import StringProperty, FloatProperty, EnumProperty
    from bpy.types import Operator


    format_prop_items = (
    ('OPEN_EXR', "EXR", "Save images to EXR format (up to 5 textures)"),
    ('PNG', "PNG", "Save images to PNG format")
    )

    class ImportIES(Operator, ImportHelper):
    """Import IES lamp data and generate a node group for cycles"""
    bl_idname = "import_lamp.ies"
    bl_label = "Import IES to Cycles"

    filter_glob = StringProperty(default="*.ies", options={'HIDDEN'})

    lamp_strength = FloatProperty(
    name="Strength",
    description="Multiplier for lamp strength",
    default=1.0,
    )

    image_format = EnumProperty(
    name='Convert to',
    items=format_prop_items,
    default='PNG',
    )

    def execute(self, context):
    return read_lamp_data(self.report, self.filepath, self.lamp_strength,
    self.image_format)


    class ExportLampEXR(Operator, ExportHelper):
    """Export IES lamp data in EXR format"""
    bl_idname = "import_lamp.gen_exr"
    bl_label = "Export lamp to image"

    image_name = StringProperty(options={'HIDDEN'})
    intensity = FloatProperty(options={'HIDDEN'})
    lamp_cone_type = EnumProperty(
    items=(('TYPE90', "0-90", "Angles from 0 to 90 degrees"),
    ('TYPE180', "0-180", "Angles from 0 to 90 degrees")),
    options={'HIDDEN'}
    )
    image_format = EnumProperty(items=format_prop_items, options={'HIDDEN'})
    use_filter_image = True

    def execute(self, context):
    return add_img(self.image_name, self.filepath, self.intensity,
    self.lamp_cone_type, self.image_format)
    # self.report({'ERROR'}, "Could not make new image %s at %s" %
    # (self.image_name, self.filepath))

    def invoke(self, context, event):
    if self.image_format == 'PNG':
    self.filename_ext = ".png"
    # self.filter_glob = StringProperty(default="*.png", options={'HIDDEN'})
    else:
    self.filename_ext = ".exr"
    # self.filter_glob = StringProperty(default="*.exr", options={'HIDDEN'})

    return ExportHelper.invoke(self, context, event)


    def menu_func(self, context):
    self.layout.operator(ImportIES.bl_idname, text='IES Lamp Data (.ies)')


    def register():
    bpy.utils.register_class(ImportIES)
    bpy.utils.register_class(ExportLampEXR)
    bpy.types.INFO_MT_file_import.append(menu_func)


    def unregister():
    bpy.utils.unregister_class(ImportIES)
    bpy.types.INFO_MT_file_import.remove(menu_func)


    if __name__ == "__main__":
    register()

    bpy.ops.import_lamp.ies('INVOKE_DEFAULT')