Blender-O3D-IO-Public icon indicating copy to clipboard operation
Blender-O3D-IO-Public copied to clipboard

Blender 2.79 support for alpha clip

Open github-actions[bot] opened this issue 1 year ago • 0 comments

GENERATED BY BLENDER-O3D-IO COPYRIGHT THOMAS MATHIESON 2023 #

##############################################################

https://api.github.com/space928/Blender-O3D-IO-Public/blob/30fc61afe3d0de1faf0f0d5f75fba87ae10d4ce1/o3d_io/o3d_cfg_parser.py#L558


                        files.append((mesh_path, current_lod))
                current_mesh = line

                cfg_data[current_lod]["meshes"][current_mesh] = {
                    "path": mesh_path,
                    "matls": {},
                    "light_flares": [],
                    "interior_lights": {},
                    "spotlights": {},
                    "cfg_data": []
                }

        # elif current_command == "[viewpoint]":
        #     if param_ind == 0:
        #         cfg_data[current_lod]["meshes"][current_mesh]["viewpoint"] = (int(line), i)

        elif current_command == "[interiorlight]":
            if param_ind == -1:
                interior_light_ind += 1
                cfg_data[current_lod]["meshes"][current_mesh]["interior_lights"][interior_light_ind] = {}

                lights.append(cfg_data[current_lod]["meshes"][current_mesh]["interior_lights"][interior_light_ind])

            m_light = cfg_data[current_lod]["meshes"][current_mesh]["interior_lights"][interior_light_ind]
            if param_ind == 0:
                m_light["variable"] = line
            elif param_ind == 1:
                m_light["range"] = float(line)
            elif param_ind == 2:
                m_light["red"] = float(line) / 255
            elif param_ind == 3:
                m_light["green"] = float(line) / 255
            elif param_ind == 4:
                m_light["blue"] = float(line) / 255
            elif param_ind == 5:
                m_light["x_pos"] = float(line)
            elif param_ind == 6:
                m_light["y_pos"] = float(line)
            elif param_ind == 7:
                m_light["z_pos"] = float(line)
                m_light["last_line"] = i

        elif current_command == "[spotlight]":
            if param_ind == -1:
                spotlight_ind += 1
                cfg_data[current_lod]["meshes"][current_mesh]["spotlights"][spotlight_ind] = {}

                lights.append(cfg_data[current_lod]["meshes"][current_mesh]["spotlights"][spotlight_ind])

            m_light = cfg_data[current_lod]["meshes"][current_mesh]["spotlights"][spotlight_ind]
            if param_ind == 0:
                m_light["x_pos"] = float(line)
            elif param_ind == 1:
                m_light["y_pos"] = float(line)
            elif param_ind == 2:
                m_light["z_pos"] = float(line)
            elif param_ind == 3:
                m_light["x_fwd"] = float(line)
            elif param_ind == 4:
                m_light["y_fwd"] = float(line)
            elif param_ind == 5:
                m_light["z_fwd"] = float(line)
            elif param_ind == 6:
                m_light["col_r"] = float(line) / 255
            elif param_ind == 7:
                m_light["col_g"] = float(line) / 255
            elif param_ind == 8:
                m_light["col_b"] = float(line) / 255
            elif param_ind == 9:
                m_light["range"] = float(line)
            elif param_ind == 10:
                m_light["inner_angle"] = float(line)
            elif param_ind == 11:
                m_light["outer_angle"] = float(line)

        elif current_command == "[light_enh]":
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["light_flares"].append({"type": "[light_enh]"})
            light_flare = cfg_data[current_lod]["meshes"][current_mesh]["light_flares"][-1]
            if param_ind == 0:
                light_flare["x_pos"] = float(line)
            elif param_ind == 1:
                light_flare["y_pos"] = float(line)
            elif param_ind == 2:
                light_flare["z_pos"] = float(line)
            elif param_ind == 3:
                light_flare["col_r"] = float(line) / 255
            elif param_ind == 4:
                light_flare["col_g"] = float(line) / 255
            elif param_ind == 5:
                light_flare["col_b"] = float(line) / 255
            elif param_ind == 6:
                light_flare["size"] = float(line)
            elif param_ind == 7:
                light_flare["brightness_var"] = line
            elif param_ind == 8:
                light_flare["brightness"] = float(line)
            elif param_ind == 9:
                light_flare["z_offset"] = float(line)
            elif param_ind == 10:
                light_flare["effect"] = int(line)
            elif param_ind == 11:
                light_flare["ramp_time"] = float(line)
            elif param_ind == 12:
                light_flare["texture"] = line

        elif current_command == "[light_enh_2]":
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["light_flares"].append({"type": "[light_enh_2]"})
            light_flare = cfg_data[current_lod]["meshes"][current_mesh]["light_flares"][-1]
            if param_ind == 0:
                light_flare["x_pos"] = float(line)
            elif param_ind == 1:
                light_flare["y_pos"] = float(line)
            elif param_ind == 2:
                light_flare["z_pos"] = float(line)
            elif param_ind == 3:
                light_flare["x_fwd"] = float(line)
            elif param_ind == 4:
                light_flare["y_fwd"] = float(line)
            elif param_ind == 5:
                light_flare["z_fwd"] = float(line)
            elif param_ind == 6:
                light_flare["x_rot"] = float(line)
            elif param_ind == 7:
                light_flare["y_rot"] = float(line)
            elif param_ind == 8:
                light_flare["z_rot"] = float(line)
            elif param_ind == 9:
                light_flare["omni"] = int(line) == 1
            elif param_ind == 10:
                light_flare["rotating"] = int(line)
            elif param_ind == 11:
                light_flare["col_r"] = float(line) / 255
            elif param_ind == 12:
                light_flare["col_g"] = float(line) / 255
            elif param_ind == 13:
                light_flare["col_b"] = float(line) / 255
            elif param_ind == 14:
                light_flare["size"] = float(line)
            elif param_ind == 15:
                light_flare["max_brightness_angle"] = float(line)
            elif param_ind == 16:
                light_flare["min_brightness_angle"] = float(line)
            elif param_ind == 17:
                light_flare["brightness_var"] = line
            elif param_ind == 18:
                light_flare["brightness"] = float(line)
            elif param_ind == 19:
                light_flare["z_offset"] = float(line)
            elif param_ind == 20:
                light_flare["effect"] = int(line)
            elif param_ind == 21:
                light_flare["cone_effect"] = int(line) == 1
            elif param_ind == 22:
                light_flare["ramp_time"] = float(line)
            elif param_ind == 23:
                light_flare["texture"] = line

        elif current_command == "[matl]" or current_command == "[matl_change]":
            if param_ind == 0:
                matl = line.lower()
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][matl] = {
                    "diffuse": (line, i),
                    "type": current_command,
                    "cfg_data": []
                }
                current_mat = matl
            elif param_ind == 1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["mat_id"] = int(line)
            elif current_command == "[matl_change]" and param_ind == 2:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["change_var"] = line

        elif current_command == "[matl_alpha]":
            if param_ind == 0:
                try:
                    cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["alpha"] = (int(line), i)
                except ValueError:
                    log("Found matl_alpha tag with invalid parameter! Line=" + str(i))

                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        elif current_command == "[matl_transmap]":
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["transmap"] = (line, i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        elif current_command == "[matl_envmap]":
            # TODO: Load the actual transmap
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["envmap_tex"] = line
            if param_ind == 1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["envmap"] = (float(line), i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        elif current_command == "[matl_envmap_mask]":
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["envmap_mask"] = (line, i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        elif current_command == "[matl_bumpmap]":
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["bumpmap"] = (line, i)
            if param_ind == 1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["bumpmap_strength"] = (
                    float(line), i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        elif current_command == "[alphascale]":
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["alphascale"] = (line, i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

            # alphascale is not currently exported correctly, so we'll re-add it to the cfg_data as an unparsed command
            # so that it re-exports correctly
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command] = []
            else:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command].append(line)

        elif current_command == "[matl_noZwrite]":
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["noZwrite"] = True
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

            # alphascale is not currently exported correctly, so we'll re-add it to the cfg_data as an unparsed command
            # so that it re-exports correctly
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command] = []
            else:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command].append(line)

        elif current_command == "[matl_noZcheck]":
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["noZcheck"] = True
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

            # alphascale is not currently exported correctly, so we'll re-add it to the cfg_data as an unparsed command
            # so that it re-exports correctly
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command] = []
            else:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command].append(line)

        elif current_command == "[matl_allcolor]":
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["allcolor"] = []
            elif param_ind < 14:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["allcolor"].append((float(line), i))

            if param_ind == 13:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

            # Allcolor is not currently exported correctly, so we'll re-add it to the cfg_data as an unparsed command
            # so that it re-exports correctly
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command] = []
            else:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat][current_command].append(line)

        elif current_command == "[matl_nightmap]":
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["nightmap"] = (line, i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        elif current_command == "[matl_lightmap]":
            if param_ind == 0:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["lightmap"] = (line, i)
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["last_line"] = i

        # Unused commands are parsed and stored in the cfg_data dictionary, but they keep their square brackets so they
        # can be differentiated later.
        elif current_mat is not None:
            # Current command is not currently parsed
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["cfg_data"].append([current_command])
            else:
                cfg_data[current_lod]["meshes"][current_mesh]["matls"][current_mat]["cfg_data"][-1].append(line)

        elif current_mesh is not None:
            # Current command is not currently parsed
            if param_ind == -1:
                cfg_data[current_lod]["meshes"][current_mesh]["cfg_data"].append([current_command])
            else:
                cfg_data[current_lod]["meshes"][current_mesh]["cfg_data"][-1].append(line)

        elif current_lod is not None and current_command is not None:
            # Current command is not currently parsed
            if param_ind == -1:
                cfg_data[current_lod]["cfg_data"].append([current_command])
            else:
                cfg_data[current_lod]["cfg_data"][-1].append(line)

    return cfg_data, (folder + "\\")


def write_additional_cfg_props(cfg_props, f):
    if "cfg_data" in cfg_props:
        for prop in cfg_props["cfg_data"]:
            f.write(prop[0] + "\n")
            f.write("\n".join(prop[1:]))
            f.write("\n")

            if len(prop) > 1 and prop[-1].strip() != "":
                f.write("\n")


def write_cfg_mesh(f, filepath, context, obj):
    if "export_path" in obj:
        export_path = obj["export_path"]
    else:
        export_path = obj.name + ".o3d"

    f.write("---------------------------------------------------------------\n"
            "\t\t{0}\n\n".format(export_path[:-4].upper()))
    f.write("[mesh]\n{0}\n\n".format(export_path))

    write_additional_cfg_props(obj.data, f)

    # Materials
    write_cfg_materials(f, obj)


def col_float_to_int(col):
    """
    Converts a Blender colour (rgb in the 0-1 range into the 0-255 (int) range)
    :param col:
    :return:
    """
    return (max(min(int(x*255), 255), 0) for x in col)


def write_cfg_empty(f, filepath, context, obj):
    if "type" not in obj:
        return

    pos = obj.location

    f.write("----------\n")
    if obj["type"] == "[light_enh]":
        texture = os.path.basename(obj.data.filepath)
        if texture == "licht.bmp":
            texture = ""

        if bpy.app.version < (2, 80):
            size = obj.empty_draw_size
        else:
            size = obj.empty_display_size

        f.write("[light_enh]\n")
        f.write("\n".join(map(str, (*pos[:3],
                                    *col_float_to_int(obj.color[:3]),
                                    size,
                                    obj["brightness_var"], obj["brightness"],
                                    obj["z_offset"], obj["effect"], obj["ramp_time"],
                                    texture))))
        f.write("\n\n")
    elif obj["type"] == "[light_enh_2]":
        texture = os.path.basename(obj.data.filepath)
        if texture == "licht.bmp":
            texture = ""

        if bpy.app.version < (2, 80):
            size = obj.empty_draw_size
        else:
            size = obj.empty_display_size

        f.write("[light_enh_2]\n")
        f.write("\n".join(map(str, (*pos[:3],
                                    *obj["forward_vector"],
                                    *obj["rotation_axis"],
                                    1 if obj["omnidirectional"] else 0,
                                    obj["rotating"],
                                    *col_float_to_int(obj.color[:3]),
                                    size,
                                    obj["max_brightness_angle"], obj["min_brightness_angle"],
                                    obj["brightness_var"], obj["brightness"],
                                    obj["z_offset"], obj["effect"], obj["cone_effect"], obj["ramp_time"],
                                    texture))))
        f.write("\n\n")

    write_additional_cfg_props(obj.data, f)


def write_cfg_light(f, filepath, context, obj):
    f.write("----------\n")
    if obj.data.type == "SPOT":
        spot_vec = mathutils.Vector((0, 0, 1))
        eul = mathutils.Euler(obj.rotation_euler, "XYZ")
        spot_vec.rotate(eul)
        spot_vec[2] = -spot_vec[2]
        spot_vec[1] = -spot_vec[1]

        f.write("[spotlight]\n")
        f.write("\n".join(map(str, (*obj.location,
                                    *spot_vec,
                                    *col_float_to_int(obj.data.color[:3]),
                                    obj.data.distance if bpy.app.version < (2, 80) else obj.data.energy / 10,
                                    math.degrees(obj.data.spot_size * obj.data.spot_blend),
                                    math.degrees(obj.data.spot_size)))))
        f.write("\n\n")
    elif obj.data.type == "POINT":
        if "variable" in obj.data:
            light_var = obj.data["variable"]
        else:
            light_var = ""
        f.write("[interiorlight]\n")
        f.write("\n".join(map(str, (light_var,
                                    obj.data.distance if bpy.app.version < (2, 80) else obj.data.energy / 10,
                                    *col_float_to_int(obj.data.color[:3]),
                                    *obj.location))))
        f.write("\n\n")

    write_additional_cfg_props(obj.data, f)


def write_cfg_materials(f, obj):
    for i, mat_blender in enumerate(obj.data.materials):
        mat = o3d_node_shader_utils.PrincipledBSDFWrapper(mat_blender, is_readonly=True)
        if mat.base_color_texture is not None and mat.base_color_texture.image is not None:
            f.write("----------\n")
            if "type" in mat_blender and mat_blender["type"] == "[matl_change]":
                f.write("[matl_change]\n{0}\n{1}\n{2}\n\n".format(
                    os.path.basename(mat.base_color_texture.image.filepath),
                    i,
                    mat_blender["change_var"]))
            else:
                f.write("[matl]\n{0}\n{1}\n\n".format(os.path.basename(mat.base_color_texture.image.filepath), i))

            transmap = False
            alpha_mode = 0
            envmap = False
            specular = 0
            bumpmap = None
            emission_tex = None
            if bpy.app.version < (2, 80):
                for tex in mat_blender.texture_slots:
                    if tex.image.use_map_alpha:
                        alpha_mode = 2
                        # TODO: Blender 2.79 support for alpha clip
                        if not tex.image.use_map_color_diffuse:
                            transmap = True

                if mat_blender.specular_hardness > 20:
                    # Heuristic to determine when we might want to turn on the envmap
                    envmap = True

                specular = math.sqrt(mat_blender.specular_intensity)
            else:
                if mat.alpha_texture.image is not None:
                    alpha_mode = 2
                    if mat_blender.blend_method == "CLIP":
                        alpha_mode = 1

                    if mat.alpha_texture.image.filepath != mat.base_color_texture.image.filepath:
                        transmap = True

                if mat.roughness <= 0.1:
                    envmap = True

                specular = math.sqrt(mat.specular)

                if mat.normalmap_texture is not None and mat.normalmap_texture.image is not None:
                    bumpmap = os.path.basename(mat.normalmap_texture.image.filepath)

                if mat.emission_color_texture is not None and mat.emission_color_texture.image is not None:
                    emission_tex = os.path.basename(mat.emission_color_texture.image.filepath)

            if alpha_mode > 0:
                f.write("[matl_alpha]\n{0}\n\n".format(alpha_mode))

            if transmap:
                f.write("[matl_transmap]\n{0}\n\n".format(os.path.basename(mat.alpha_texture.image.filepath)))

            if envmap and specular > 0.01:
                envmap_tex = "envmap.bmp"
                if "envmap_tex" in mat_blender:
                    envmap_tex = mat_blender["envmap_tex"]

                f.write("[matl_envmap]\n{0}\n{1}\n\n".format(envmap_tex, specular))

                if bpy.app.version > (2, 79):
                    if not transmap and mat.specular_texture is not None and mat.specular_texture.image is not None:
                        f.write(
                            "[matl_envmap_mask]\n{0}\n\n".format(os.path.basename(mat.specular_texture.image.filepath)))

            if bumpmap is not None:
                f.write("[matl_bumpmap]\n{0}\n{1}\n\n".format(bumpmap, mat.normalmap_strength))

            if emission_tex is not None:
                # Again, this is a bit of a rubbish heuristic, it should be improved at some point
                if mat.emission_strength > 1:
                    f.write("[matl_lightmap]\n{0}\n\n".format(emission_tex))
                else:
                    f.write("[matl_nightmap]\n{0}\n\n".format(emission_tex))

            write_additional_cfg_props(mat_blender, f)


def write_cfg_object(context, f, filepath, obj):
    o_type = obj.type
    if o_type == "MESH":
        write_cfg_mesh(f, filepath, context, obj)
    elif o_type == "LIGHT":
        write_cfg_light(f, filepath, context, obj)
    elif o_type == "LAMP":
        write_cfg_light(f, filepath, context, obj)
    elif o_type == "EMPTY":
        write_cfg_empty(f, filepath, context, obj)
    else:
        log("Unsupported object type for export: {0} for {1}".format(obj.type, obj.name))


def write_cfg(filepath, objs, context):
    """
    Attempts to merge blender objects into an existing CFG/SCO file
    :param context: the current Blender context
    :param filepath: path to the cfg file, if it doesn't exist a new one will be created
    :param objs: the array of Blender objects to export
    :return:
    """

    if True or not os.path.isfile(filepath):
        with open(filepath, "w") as f:
            log("Writing cfg file...")
            scene = bpy.data.scenes[context.scene.name]
            cfg_props = scene["cfg_data"]
            if "groups" not in scene:
                scene["groups"] = {
                    "ind": 1,
                    "group": "BlenderExport"
                }
            if "friendlyname" not in scene:
                scene["friendlyname"] = os.path.basename(filepath)[:-4]

            # Create a new minimal CFG file
            f.write("""
###############################################################
# GENERATED BY BLENDER-O3D-IO COPYRIGHT THOMAS MATHIESON 2023 #
###############################################################

[groups]
{0}
{1}

[friendlyname]
{2}
    
""".format(scene["groups"]["ind"], scene["groups"]["group"], scene["friendlyname"]))

            # Populate any cfg data in the scene
            write_additional_cfg_props(scene, f)

            f.write("\n###############################################################\n"
                    "\t\tBEGIN MODEL DATA\n"
                    "###############################################################\n\n")

            # Now write each lod and each mesh within those lods
            if bpy.app.version < (2, 80):
                group_names = [x.name for x in bpy.data.groups]
                lods = [(x, float(x[4:])) for x in group_names if x.startswith("LOD")]
                lods = sorted(lods, key=lambda x: x[1], reverse=True)
                non_lods = [o for x in group_names for o in bpy.data.groups[x].objects if
                            not x.startswith("LOD")]

                non_lods.extend(context.scene.objects)
            else:
                collection_names = [x.name for x in bpy.data.collections]
                lods = [(x, float(x[4:])) for x in collection_names if x.startswith("LOD")]
                lods = sorted(lods, key=lambda x: x[1], reverse=True)
                non_lods = [o for x in collection_names for o in bpy.data.collections[x].all_objects if
                            not x.startswith("LOD")]

                non_lods.extend(context.scene.collection.objects)

            for obj in non_lods:
                # log("Exporting {0}...".format(obj.name))
                write_cfg_object(context, f, filepath, obj)

            for lod in lods:
                f.write("\n###############################################################\n"
                        "\t\tBEGIN LOD {0}\n"
                        "###############################################################\n\n".format(lod[1]))
                f.write("[LOD]\n{0}\n\n".format(lod[1]))

                if bpy.app.version < (2, 80):
                    objs = bpy.data.groups[lod[0]].objects
                else:
                    objs = bpy.data.collections[lod[0]].all_objects

                for obj in objs:
                    # log("Exporting {0}...".format(obj.name))
                    write_cfg_object(context, f, filepath, obj)

github-actions[bot] avatar Apr 18 '23 02:04 github-actions[bot]