Exemplo n.º 1
0
def build(root, models, links_simple, links_revert, shapes_ids):
    """
	Put shapes in the scene and apply the correct transforms.
	"""
    if config.verbose: print("models_builder_fbx launched")

    comment = etree.Comment("Models.")
    root.append(comment)

    # Create a dictionnary of models to pass through every parent
    models_id = {}
    for model in models:
        id, type, obj = model.get("value").replace("::", "").split(",")
        models_id[id] = tools.getProperties(model)

    for model in models:
        id, type, obj = model.get("value").replace("::", "").split(",")
        if id in links_simple:
            for link in links_simple[id]:
                if link in shapes_ids:  # Only shapes (for now ?)
                    curr_shape = etree.Element("shape")
                    curr_shape.set("type", "instance")
                    curr_ref = etree.SubElement(curr_shape, "ref")
                    curr_ref.set("id", link)
                    recursive_build_hierarchy(root, curr_shape, links_simple,
                                              links_revert, models_id, id)

        elif config.verbose:
            print("id " + id + " not in links_simple")
Exemplo n.º 2
0
def build(root, textures, links_param_revert):
    """
	Creates textures with in the scene.
	Return a list of texture ids.
	"""
    verbose = config.verbose

    if verbose: print("textures_builder_fbx launched")
    comment = etree.Comment("Textures with ids")
    root.append(comment)

    textures_id = {}

    if textures != [] and not os.path.exists(config.filepath +
                                             "export\\textures"):
        os.makedirs(config.filepath + "export\\textures")
    copyfile(config.curr_place + "\\missing_texture.png",
             config.filepath + "export\\textures\\missing_texture.png")

    # Go through all textures in the scene
    for texture in textures:
        id, type, obj = texture.get("value").replace("::", "").split(",")
        rel_reference = texture.find("RelativeFilename").text.strip()
        abs_reference = texture.find("FileName").text.strip()

        properties = tools.getProperties(texture)
        uoff, voff = properties["Translation"][
            -3:-1] if "Translation" in properties else ["0", "0"]
        uscaling, vscaling = properties["Scaling"][
            -3:-1] if "Scaling" in properties else ["1", "1"]

        if rel_reference == "" and abs_reference == "":
            if verbose:
                print("Empty reference for id " + id +
                      ", replacing with error texture")
            reference = "textures\\missing_texture.png"

        # Try relative path then absolute if it did'nt work
        elif not Path(config.filepath + rel_reference).is_file():
            if not Path(abs_reference).is_file():
                if verbose: print("Missing texture : " + rel_reference)
                reference = "textures\\missing_texture.png"
            else:
                new_reference = "textures\\" + id + "." + abs_reference.split(
                    ".")[-1]
                copyfile(abs_reference,
                         config.filepath + "export\\" + new_reference)
                reference = new_reference
        else:
            new_reference = "textures\\" + id + "." + abs_reference.split(
                ".")[-1]
            copyfile(config.filepath + rel_reference,
                     config.filepath + "export\\" + new_reference)
            reference = new_reference

        if id not in links_param_revert:
            if verbose:
                print("Texture " + reference +
                      " never used. Not writing it to file.")
        elif any(reference.lower().endswith(s)
                 for s in [".bmp", ".jpg", ".png", ".tga", ".exr"]):
            textures_id[id] = properties
            textures_id[id]["reference"] = reference
            curr_texture = tools.create_obj(root, "texture", "bitmap", id)
            tools.set_value(curr_texture, "string", "filename", reference)

            # Avoid cluttering for the final file, removing 0 offsets and 1 scaling
            if uoff != "0":
                tools.set_value(curr_texture, "float", "uoffset", uoff)
            if voff != "0":
                tools.set_value(curr_texture, "float", "voffset", voff)
            if uscaling != "1":
                tools.set_value(curr_texture, "float", "uscale", uscaling)
            if vscaling != "-1":
                tools.set_value(curr_texture, "float", "vscale",
                                str(-float(vscaling)))
            # Textures in 3dsmax have their v values inverted compared to mitsuba.

        else:
            print("Unknown texture file type : " + reference.split(".")[-1])
            print("for file : " + reference)
    return textures_id
Exemplo n.º 3
0
def build(fbxtree):
    """
	This function builds the scene from the fbx tree.
	"""
    verbose = config.verbose
    if verbose: print("builder_fromfbx launched")

    # Get information about the vertical axis used
    globalsettings = tools.getProperties(fbxtree.find("GlobalSettings"))
    config.upvector = "0 1 0" if globalsettings["UpAxis"][
        -1] == "1" else "0 0 1"

    objects = fbxtree.find("Objects")

    # Separate objects into different categories
    models = objects.findall("Model")
    geometries = objects.findall("Geometry")
    materials = objects.findall("Material")
    nodes = objects.findall(
        "NodeAttribute")  # Contains some cameras and lights properties
    # videos    = objects.findall("Video") # Not sure about what it contains, it seems to be a duplicate of “textures”. Maybe for compatibility reasons ?
    textures = objects.findall("Texture")
    nulls = objects.findall("Null")  # Useful for cameras and spot targets
    """ Useless for now
	# Animation. Will probably not be usable
	anim_c_n  = objects.findall("AnimationCurveNode")
	anim_c    = objects.findall("AnimationCurve")

	# Maybe useful. Not sure what to do with this.
	implem    = objects.find("Implementation")
	bind_table= objects.find("BindingTable")
	bind_oper = objects.find("BindingOperator")
	"""

    # Useful to link objects, textures to materials, materials to models, and geometry to models.
    connections_list = fbxtree.find("Connections")
    comments = connections_list.findall("comment")
    links = connections_list.findall("C")

    # Stocking links between objects.
    # Links simple and links revert are simple links, for example for hierarchies of objects.
    # Links param and links param revert are for parameters, for example, a texture referenced by a material.
    links_simple, links_revert, links_param, links_param_revert = tools.extract_links(
        links)

    root = etree.Element("scene")
    root.set("version", "0.5.0")

    # Set up the integrator. I chose pathtracing by default TODO add as a parameter
    tools.create_obj(root, "integrator", "path")

    if not os.path.exists(config.filepath + "export"):
        os.makedirs(config.filepath + "export")

    # All the functions will directly add their elements to the root element
    light_cam_builder.build(root, nodes, models, nulls, links_simple,
                            links_param)
    textures_id = textures_builder.build(root, textures, links_param_revert)
    materials_ids = materials_builder.build(root, materials, textures_id,
                                            links_param, links_param_revert)
    shapes_ids = shapes_builder.build(root, geometries, materials_ids,
                                      links_simple, links_revert)
    models_builder.build(root, models, links_simple, links_revert, shapes_ids)

    if verbose: print("Writing to file…")
    with open(config.filepath + "export\\" + config.filename + ".xml",
              "w",
              encoding="utf8") as outputfile:
        if verbose: print("prettifying… (Can take a while for big files)")
        stringout = tools.prettifyXml(etree.tostring(root).decode())
        if verbose: print("writing…")
        outputfile.write(stringout)
Exemplo n.º 4
0
def build(root, geometries, materials_ids, links_simple, links_revert):
    """
	Exports all the geometry into a folder for later use in the scene.
	Return a list of ids for the geometry
	"""
    if config.verbose: print("shapes_builder_fbx launched")
    savepath = config.filepath + "export\\meshes\\"
    if not os.path.exists(savepath):
        os.makedirs(savepath)

    comment = etree.Comment("Shapes.")
    root.append(comment)

    geometries_id = []
    # Only keep geometries with polygonvertex
    geometries = [
        geometry for geometry in geometries
        if geometry.find("PolygonVertexIndex") != None
    ]

    for geometry in geometries:

        id, type, obj = geometry.get("value").replace("::", "").split(", ")
        if obj != "Mesh":
            print("Unknown Geometry obj : " + obj + " (id=" + id + ")")
        geometries_id.append(id)

        if id not in links_revert:
            if verbose:
                print("Model " + id + " never used. Not writing it to file.")
        else:
            properties = tools.getProperties(geometry)
            linked_materials = links_revert[id] if id in links_revert else [
            ]  # Get the model(s) containing this geometry (Only the models reference materials)
            if len(linked_materials) == 1:
                linked_materials = links_simple[linked_materials[0]]
            else:
                print("Multiple models containing geometry " + id + " ???")
                linked_materials = []
            linked_materials = [
                link for link in linked_materials if link in materials_ids
            ]  #Only keep ids of materials
            # I think that the index for materials is based on the order they appear in the file
            # Maybe not, but i see no other info in the fbx to know that, so that's what i use and it seems to work

            vertices_data = geometry.find("Vertices")
            nb_vertices = int(vertices_data.get("value").replace("*", ""))

            polygons_data = geometry.find("PolygonVertexIndex")
            nb_poly_ind = int(polygons_data.get("value").replace("*", ""))

            edges_data = geometry.find("Edges")
            nb_edges = int(edges_data.get("value").replace("*", ""))

            normal_layer = geometry.find("LayerElementNormal")
            if normal_layer != None:
                normals_data = normal_layer.find("Normals")
                normalsW_data = normal_layer.find("NormalsW")
                nb_normals = int(normals_data.get("value").replace("*", ""))
                nb_normalsW = int(normalsW_data.get("value").replace("*", ""))

            uv_layer = geometry.find("LayerElementUV")
            if uv_layer != None:
                uv_data = uv_layer.find("UV")
                uv_index_data = uv_layer.find("UVIndex")
                nb_uv_data = int(uv_data.get("value").replace("*", ""))
                nb_uv_index = int(uv_index_data.get("value").replace("*", ""))

            material_layer = geometry.find("LayerElementMaterial")
            if material_layer != None:
                material_data = list(
                    map(
                        int,
                        material_layer.find("Materials").find("a").text.split(
                            ",")))
                if material_layer.find(
                        "MappingInformationType").text == "AllSame":
                    material_data = nb_poly_ind * [material_data[0]]

            vertices_in = [
                tools.str2float2str(num)
                for num in vertices_data.find("a").text.split(",")
            ]
            polygons_in = list(
                map(int,
                    polygons_data.find("a").text.split(",")))
            edges_in = [
                tools.str2float2str(num)
                for num in edges_data.find("a").text.split(",")
            ]
            normals_in = [
                tools.str2float2str(num)
                for num in normals_data.find("a").text.split(",")
            ]
            normalsW_in = [
                tools.str2float2str(num)
                for num in normalsW_data.find("a").text.split(",")
            ]
            uv_in = [
                tools.str2float2str(num)
                for num in uv_data.find("a").text.split(",")
            ]
            uv_index_in = list(
                map(int,
                    uv_index_data.find("a").text.split(",")))

            vertices, polygon_vertex_index, normals, uv = [], [], [], []

            if nb_vertices % 3 != 0:
                print("Points values not a multiple of 3 !")
            for i in range(int(nb_vertices / 3)):
                vertices.append(vertices_in[3 * i:3 * i + 3])

            curr_vertex = []
            for index in polygons_in:
                if index < 0:
                    curr_vertex.append(-index - 1)
                    polygon_vertex_index.append(curr_vertex)
                    curr_vertex = []
                else:
                    curr_vertex.append(index)
            nb_polygons = len(polygon_vertex_index)

            if normal_layer != None:
                normal_type = normal_layer.find(
                    "ReferenceInformationType").text
                if normal_type == "Direct":
                    for i in range(int(nb_normals / 3)):
                        normals.append(normals_in[3 * i:3 * i + 3])
                elif normal_type == "IndexToDirect":
                    # TODO
                    print("NORMAL INDEXTODIRECT DOESN'T WORK RIGHT NOW")
                else:
                    print(
                        "Unknown ReferenceInformationType for normal in obj " +
                        id)
            else:
                print("NO NORMALS for object with id " + id)

            if uv_layer != None:
                uv_type = uv_layer.find("ReferenceInformationType").text
                if uv_type == "Direct":
                    for i in range(int(nb_uv_data / 2)):
                        uv.append(uv_in[2 * i:2 * i + 2])
                elif uv_type == "IndexToDirect":
                    for i in range(int(nb_uv_index)):
                        index = uv_index_in[i]
                        uv.append(uv_in[2 * index:2 * index + 2])
                else:
                    print("Unknown ReferenceInformationType for uv in obj " +
                          id)
                    uv = ["0 0"] * nb_poly_ind
            else:
                if config.verbose:
                    print("No uv for object with id " + id +
                          ". Using default of 0, 0")
                uv = ["0 0"] * nb_poly_ind

            materials = geometry.find("LayerElementMaterial")
            if materials != None:
                materials = list(
                    map(int,
                        materials.find("Materials").find("a").text.split(",")))
            if materials == None or len(materials) <= 1:
                materials = [0 for i in range(nb_polygons)]
            max_material = max(materials)

            # The shapegroup will contain all meshes with different materials, and allow instanciation
            shapegroup = tools.create_obj(root, "shape", "shapegroup", id)

            # Initialize
            for i in range(max_material + 1):

                vertex_text = []
                poly_index = []
                total_index = 0
                curr_polygon_vertex_num = 0

                for j in range(len(polygon_vertex_index)):
                    vertex_indexes = polygon_vertex_index[j]
                    curr_poly_index = []
                    for k in range(len(vertex_indexes)):

                        # Only keep polygons with the current material
                        if material_layer == None or material_data[j] == i:
                            curr_poly_index.append(str(total_index))
                            total_index += 1
                            vertex_index = vertex_indexes[k]
                            curr_vertex_text = " ".join(
                                vertices[vertex_index]) + " "
                            curr_vertex_text += " ".join(
                                normals[curr_polygon_vertex_num]) + " "
                            curr_vertex_text += " ".join(
                                uv[curr_polygon_vertex_num])
                            vertex_text.append(curr_vertex_text)
                        curr_polygon_vertex_num += 1

                    # Generate multiple triangle to replace polygons (not supported by Mitsuba Renderer)
                    if len(curr_poly_index) > 3:
                        for k in range(len(curr_poly_index) - 2):
                            curr_poly = [curr_poly_index[0]
                                         ] + curr_poly_index[k + 1:k + 3]
                            poly_index.append(curr_poly)
                    elif len(curr_poly_index) > 0:
                        poly_index.append(curr_poly_index)

                if vertex_text != []:  # Export only non-empty objects
                    output = open(savepath + id + "_" + str(i) + ".ply", "w")
                    output.write("ply\n")
                    output.write("format ascii 1.0\n")
                    output.write("element vertex " + str(len(vertex_text)) +
                                 "\n")
                    output.write("""property float32 x
property float32 y
property float32 z
property float32 nx
property float32 ny
property float32 nz
property float32 u
property float32 v
""")
                    output.write("element face " + str(len(poly_index)) + "\n")
                    output.write("property list uint8 int32 vertex_indices\n")
                    output.write("end_header\n")

                    for vert in vertex_text:
                        output.write(vert + "\n")

                    for poly in poly_index:  # Only triangles
                        output.write("3 " + " ".join(poly) + "\n")
                    shape = tools.create_obj(shapegroup, "shape", "ply")
                    tools.set_value(shape, "string", "filename",
                                    "meshes/" + id + "_" + str(i) + ".ply")

                    try:
                        tools.set_value(shape, "ref", "bsdf",
                                        linked_materials[i])
                    except IndexError:
                        if config.verbose:
                            print("No material found for object " + id +
                                  ", index " + str(i))

    return geometries_id
Exemplo n.º 5
0
def build(root, nodes, models, nulls, links_simple, links_param):
    """
	Creates lights and cameras in the scene
	"""
    verbose = config.verbose
    debug = config.debug
    if verbose: print("lightsandcameras_builder_fbx launched")

    comment = etree.Comment("Lights and cameras")
    root.append(comment)

    # Prepare nodes properties referenced by id for easy access
    camera_nodes, light_nodes = {}, {}
    for node in nodes:
        id, type, obj = node.get("value").replace("::", "").split(", ")
        properties = tools.getProperties(node)
        # named by id to find easily when needed
        node.tag = id
        if obj == "Camera":
            camera_nodes[id] = properties
        elif obj == "Light":
            light_nodes[id] = properties

    # Prepare models properties referenced by id for easy access
    camera_models, light_models, null_models = {}, {}, {}
    for model in models:
        id, type, obj = model.get("value").replace("::", "").split(", ")
        properties = tools.getProperties(model)
        # named by id to find easily when needed
        node.tag = id
        if obj == "Camera":
            camera_models[id] = properties
        elif obj == "Light":
            light_models[id] = properties
        elif obj == "Null":
            null_models[id] = properties

    # Lights
    # Every light placed in the scene is referenced by a model.
    # This model should be linked to a node containing all the light's properties.
    # Once we retrieved it, we are able to create the light correctly
    for id, light_model in light_models.items():
        if len(links_simple[id]) != 1:
            print("light_cam_builder_fbx : " + len(links_simple[id]) +
                  " node(s) for light model " + id)
        light_node = light_nodes[links_simple[id][0]]

        light_is_a_spot = "LookAtProperty" in links_param[
            id]  # LightType is present when the light has a target

        if "3dsMax|FSphereExtParameters|light_radius" in light_node:  # This parameter means that the light is a sphere
            light_is_a_sphere = True
            sphere_radius = float(
                light_node["3dsMax|FSphereExtParameters|light_radius"][-1])
        else:
            light_is_a_sphere = False

        # This is a mess. TODO clean all this, it's not legible
        if "3dsMax|FAreaParameters" in light_node:  #TODO area lights
            print("Area lights not supported yet, light with id " + id +
                  "will be considered as a point light for now")
            colors = light_node["Color"][-3:] if "Color" in light_node else [
                "1", "1", "1"
            ]
        elif light_node["3dsMax|" + (
            ("T" if light_is_a_spot else "F") +
            ("Sphere" if light_is_a_sphere else "Point") + "Parameters") +
                        "|useKelvin"][-1] == "1":
            colors = tools.kelvin2rgb(
                float(light_node["3dsMax|" +
                                 ("FSphereParameters" if light_is_a_sphere else
                                  "FPointParameters") + "|kelvin"][-1]))
            # TODO color filter
        else:
            colors = light_node["Color"][-3:] if "Color" in light_node else [
                "1"
            ]

        # Divide intensity by apparent surface of the sphere (disc) if it's not a point
        intensity = float(light_node["Intensity"][-1]) / (
            2. * math.pi * sphere_radius**2. if light_is_a_sphere else 1)
        rvb = []
        for color in colors:
            rvb.append(str(intensity * float(color)))

        if light_is_a_sphere:
            light_shape = tools.create_obj(root, "shape", "sphere")
            tools.set_value(light_shape, "float", "radius", str(sphere_radius))

        light = tools.create_obj(
            parent=light_shape if light_is_a_sphere else root,
            object="emitter",
            type="spot"
            if light_is_a_spot else "area" if light_is_a_sphere else "point")

        tools.set_value(
            light, "spectrum", "radiance" if light_is_a_sphere
            and not light_is_a_spot else "intensity", " ".join(rvb))

        if light_is_a_spot:
            # angles are divided by 2 because 3dsMax expresses the total angle, and mitsuba the angle from the center of the ray
            tools.set_value(light, "float", "cutoffAngle",
                            str(.5 * float(light_node["OuterAngle"][-1])))
            tools.set_value(light, "float", "beamWidth",
                            str(.5 * float(light_node["InnerAngle"][-1])))
            tools.transform_lookat(
                light, " ".join(light_model["Lcl Translation"][-3:]),
                " ".join(null_models[links_param[id]["LookAtProperty"]]
                         ["Lcl Translation"][-3:]))
        else:
            tools.transform_object(light_shape if light_is_a_sphere else light,
                                   light_model)

    # Handle cameras
    for id, camera_model in camera_models.items():
        if len(links_simple[id]) != 1:
            print("light_cam_builder_fbx : " + str(len(links_simple[id])) +
                  " node(s) for camera model " + id)
        camera_node = camera_nodes[links_simple[id][0]]
        curr_camera = tools.create_obj(root, "sensor", "perspective")

        curr_film = tools.create_obj(curr_camera, "film", "hdrfilm")
        tools.set_value(curr_film, "integer", "width",
                        camera_node["AspectWidth"][-1])
        tools.set_value(curr_film, "integer", "height",
                        camera_node["AspectHeight"][-1])

        rfilter = tools.create_obj(curr_film, "rfilter", "box")

        # Set up with nice values. TODO parameters to control this
        curr_sampler = tools.create_obj(curr_camera, "sampler", "ldsampler")
        tools.set_value(curr_sampler, "integer", "sampleCount", "64")

        tools.transform_lookat_from_properties(curr_camera, camera_node)

        tools.set_value(curr_camera, "float", "fov",
                        camera_node["FieldOfView"][-1])
        tools.set_value(curr_camera, "string", "fovAxis",
                        "x")  # Max expresses the horizontal fov
Exemplo n.º 6
0
def build(root, materials, textures_id, links_param, links_param_revert):
	"""
	Creates materials with ids in the scene.
	Returns a list of materials id.
	"""
	if config.verbose : print("materials_builder_fbx launched")
	comment = etree.Comment("Materials with ids")
	root.append(comment)

	material_ids = []

	for material in materials :
		id, type, obj = material.get("value").replace("::","").split(",")
		material_ids.append(id)

		# Get linked parameters (only textures ?)
		linked = links_param[id] if id in links_param else []
		properties = tools.getProperties(material)

		diffuse_color  =(" ".join(properties["Diffuse"][-3:])         if "Diffuse"           in properties
		else(" ".join(properties["DiffuseColor"][-3:])    if "DiffuseColor"      in properties else "1 0 0")) #Use red if there is no diffuse, for debug
		specular_color = " ".join(properties["Specular"][-3:])        if "Specular"          in properties else "1 1 1"
		shininess      =          properties["ShininessExponent"][-1] if "ShininessExponent" in properties else ""

		# If bumpmap is on, use it
		if ("3dsMax|Parameters|bump_map" in linked) :# Use bump map
			scale = properties["3dsMax|Parameters|bump_map_amt"][-1]
			curr_bumpmod = tools.create_obj(root, "bsdf", "bumpmap", id)
			set_texture(curr_bumpmod, linked, "3dsMax|Parameters|bump_map", scale)
			curr_material = etree.SubElement(curr_bumpmod, "bsdf")
		else :
			curr_material = etree.SubElement(root, "bsdf")
			curr_material.set("id", id)

		# Roughness
		if "3dsMax|Parameters|roughness" in properties :
			if "3dsMax|Parameters|roughness_map" in linked : # Use a texture
				inverted_roughness = properties["3dsMax|Parameters|roughness_inv"][-1] == "1"
				texture_properties = textures_id[linked["3dsMax|Parameters|roughness_map"]]
				filename = texture_properties["reference"].replace("\\","/").split("/")[-1]

				roughness_reference = tools.roughness_convert(filename, inverted_roughness)

				uoff, voff         = texture_properties["Translation"][-3:-1] if "Translation" in texture_properties else ["0", "0"]
				uscaling, vscaling = texture_properties["Scaling"]    [-3:-1] if "Scaling"     in texture_properties else ["1", "1"]

				# Create a new texture
				curr_roughness = tools.new_obj("texture", "bitmap")
				curr_roughness.set("name", "alpha")

				# Avoid cluttering for the final file, removing 0 offsets and 1 scaling
				if uoff     != "0" : tools.set_value(curr_roughness, "float", "uoffset", uoff)
				if voff     != "0" : tools.set_value(curr_roughness, "float", "voffset", voff)
				if uscaling != "1" : tools.set_value(curr_roughness, "float", "uscale",  uscaling)
				if vscaling !="-1" : tools.set_value(curr_roughness, "float", "vscale",  str(-float(vscaling)))

				tools.set_value(curr_roughness, "string", "filename", roughness_reference)
			else : # Use a value
				roughness = .5*float(properties["3dsMax|Parameters|roughness"][-1])
				if roughness == 0 :
					curr_roughness = None
				else :
					curr_roughness = etree.Element("float")
					curr_roughness.set("name", "alpha")
					curr_roughness.set("value", str(roughness))
		else :
			curr_roughness = None
			# Based on this blog post : https://simonstechblog.blogspot.com/2011/12/microfacet-brdf.html
			# √(2/(α+2))
			# But divided by 2, because of mitsuba's roughness scale
			roughness = .5 * (2./(float(shininess)+2.)) ** (.5)
			curr_roughness = etree.Element("float")
			curr_roughness.set("name", "alpha")
			curr_roughness.set("value", str(roughness))

		# Specular transmittance
		if "3dsMax|Parameters|transparency" in properties and float(properties["3dsMax|Parameters|transparency"][-1]) > 0 :
			transp = 1 - float(properties["3dsMax|Parameters|transparency"][-1])
			transp_color = " ".join(properties["3dsMax|Parameters|trans_color"][-4:-1])
			curr_material.set("type", "blendbsdf")

			tools.set_value(curr_material, "float", "weight", str(transp))
			# Transparent material for the blend
			curr_transp = tools.create_obj(curr_material, "bsdf", "roughdielectric")
			tools.set_value(curr_transp, "string",   "distribution",          config.distrib)
			tools.set_value(curr_transp, "spectrum", "specularTransmittance", transp_color)
			tools.set_value(curr_transp, "float",    "alpha",                 properties["3dsMax|Parameters|trans_roughness"][-1])

			if "3dsMax|Parameters|trans_ior" in properties :
				tools.set_value(curr_transp, "float", "intIOR", properties["3dsMax|Parameters|trans_ior"][-1])

			# Non-transparent part of the material
			curr_material = etree.SubElement(curr_material, "bsdf")

		# Metalness
		metalness = float(properties["3dsMax|Parameters|metalness"][-1]) if "3dsMax|Parameters|metalness" in properties else 0
		if metalness > 0 :
			if metalness < 1 :
				curr_material.set("type", "blendbsdf")
				tools.set_value(curr_material, "float", "weight",
					str(1 - float(properties["3dsMax|Parameters|metalness"][-1])))# 1 - Metalness
				curr_metal = tools.create_obj(curr_material, "bsdf", "roughconductor" if curr_roughness != None else "conductor")
			else :
				curr_material.set("type", "roughconductor" if curr_roughness != None else "conductor")
				curr_metal = curr_material
			# Metal part of the material
			tools.set_value(curr_metal, "string"  , "distribution", config.distrib)
			if curr_roughness != None :
				curr_metal.append(curr_roughness)
			tools.set_value(curr_metal, "string"  , "material"    , "none") # Perfect mirror, the color is set via specularReflectance.
			# Copy-paste from diffuse, this is what 3dsmax uses
			if "DiffuseColor" in linked :# Use a texture
				tools.set_value(curr_metal, "ref", "specularReflectance", linked["DiffuseColor"])
			elif "3dsMax|Parameters|base_color_map" in linked :
				tools.set_value(curr_metal, "ref", "specularReflectance", linked["3dsMax|Parameters|base_color_map"])
			elif diffuse_color != "1 1 1":
				tools.set_value(curr_metal, "spectrum", "specularReflectance", diffuse_color)
			# else it's the default of 1 1 1

			# Non-metal part of the material
			if metalness < 1 : curr_material = etree.SubElement(curr_material, "bsdf")

		# Non transmitting and non-metal part of the material. Only if metalness < 1
		if metalness < 1 :
			curr_material.set("type", "phong" if config.closest else ("roughplastic" if curr_roughness != None else "plastic"))
			tools.set_value(curr_material, "string", "distribution", config.distrib)

			# keep the original diffuse independantly of ior.
			if config.realist :
				tools.set_value(curr_material, "boolean", "nonlinear", "true")

			if "DiffuseColor" in linked :# Use a texture
				tools.set_value(curr_material, "ref", "diffuseReflectance", linked["DiffuseColor"])
			elif "3dsMax|Parameters|base_color_map" in linked :
				tools.set_value(curr_material, "ref", "diffuseReflectance", linked["3dsMax|Parameters|base_color_map"])
			else :
				tools.set_value(curr_material, "spectrum", "diffuseReflectance", diffuse_color)

			if specular_color != "1 1 1" : # Set up only if there is such a color
				curr_spec_color = etree.SubElement(curr_material, "spectrum")
				curr_spec_color.set("name", "specularReflectance")
				curr_spec_color.set("value", specular_color)

			if "3dsMax|Parameters|coat_ior" in properties :
				ior = properties["3dsMax|Parameters|coat_ior"][-1]
				tools.set_value(curr_material, "float", "intIOR", ior)

			if config.closest and shininess != "" :
				tools.set_value(curr_material, "float", "exponent", shininess)
			elif curr_roughness != None :
				curr_material.append(curr_roughness)

	return material_ids