Привет, имеется скрипт для Блендер 3.3.0. Пока посмотри код, я скажу в чем дело


bl_info = {
    "name": "F1 Track Material Maker",
    "author": "AlonsoBeast",
    "version": (1, 0),
    "blender": (3, 3, 0),
    "location": "3D View > Sidebar",
    "description": "Addon for managing F1 Track materials",
    "category": "Material",
}

import bpy
import os  
from bpy.props import StringProperty, CollectionProperty, EnumProperty
from bpy.types import Panel, Operator, PropertyGroup
import xml.etree.ElementTree as ET
from io import BytesIO

# Configuration ShaderGroup
SHADER_GROUP_BLOCKS = {
    "#env_diff.fx": {
        "parameterCount": "22",
        "parameterSavedCount": "3",
        "renderSortPriority": "0",
        "inputs": [
            ('7', "constant", "float", "0.000000000e+000"),
            ('17', "texture", "#diffuse_d.tga"),
            ('18', "texture", "#normal_n.tga"),
        ]
    },
    "#env_diffalpha.fx": {
        "parameterCount": "22",
        "parameterSavedCount": "3",
        "renderSortPriority": "0",
        "inputs": [
            ('7', "constant", "float", "0.000000000e+000"),
            ('17', "texture", "#alpha_d.tga"),
            ('18', "texture", "#normal_n.tga"),
        ]
    },
    "#env_diffalphaspecnorm.fx": {
        "parameterCount": "24",
        "parameterSavedCount": "5",
        "renderSortPriority": "0",
        "inputs": [
            ('7', "constant", "float", "0.000000000e+000"),
            ('17', "texture", "#diffuse_d.tga"),
            ('18', "texture", "#specular_s.tga"),
            ('19', "texture", "#normal_n.tga"),
            ('20', "texture", "#ambient_a.tga"),
        ]
    },
    "#env_diffmask.fx": {
        "parameterCount": "22",
        "parameterSavedCount": "3",
        "renderSortPriority": "0",
        "inputs": [
            ('7', "constant", "float", "0.000000000e+000"),
            ('17', "texture", "#alpha_diffuse.tga"),
            ('18', "texture", "#normal.tga"),
        ]
    },
    "#track_offtracksurface.fx": {
        "parameterCount": "42",
        "parameterSavedCount": "14",
        "renderSortPriority": "0",
        "inputs": [
            ('7', "constant", "float", "0.000000000e+000"),
            ('21', "texture", "#diffuse_tarmac_white_01_d.tga"),
            ('22', "constant", "float", "5.000000000e-001"),
            ('23', "texture", "#normal_offtrack_a_01_n.tga"),
            ('24', "constant", "float", "2.000000000e+001"),
            ('25', "constant", "float", "1.000000000e+001"),
            ('26', "texture", "#diffuse_macro_01_d.tga"),
            ('27', "texture", "#terrain_01_a.tga"),
            ('28', "texture", "#track_skidmarks_long.tga"),
            ('29', "constant", "float", "2.500000000e-001"),
            ('30', "constant", "float", "1.000000000e+000"),
            ('31', "texture", "#track_weteffects.tga"),
            ('32', "constant", "float", "1.000000000e+000"),
            ('33', "constant", "float", "1.000000000e+000"),
        ]
    },
}

# Property Group for blocks and textures
class TextureEntry(PropertyGroup):
    parameter_id: StringProperty(name="Parameter ID")
    type: StringProperty(name="Type", default="texture")
    name: StringProperty(name="Texture Name", default="")

class MaterialBlock(PropertyGroup):
    def update_shader_group(self, context):
        self.texture_inputs.clear()
        block_data = SHADER_GROUP_BLOCKS.get(self.shader_group, {})
        for input_data in block_data.get("inputs", []):
            param_id = input_data[0]
            tex_type = input_data[1]

            if tex_type == "texture":
                tex_name = input_data[2]
                tex_entry = self.texture_inputs.add()
                tex_entry.parameter_id = param_id
                tex_entry.type = tex_type
                tex_entry.name = tex_name

    shader_group: EnumProperty(
        name="Shader Group",
        items=[(key, key, "") for key in SHADER_GROUP_BLOCKS.keys()],
        update=update_shader_group
    )

    material_id: StringProperty(name="Material ID", default="new_material")
    texture_inputs: CollectionProperty(type=TextureEntry)

# UI Panel
class F1MaterialMakerPanel(Panel):
    bl_label = "F1 Track Material Maker"
    bl_idname = "VIEW3D_PT_f1_material_maker"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'F1 Material Maker'

    def draw(self, context):
        layout = self.layout
        scene = context.scene

        row = layout.row()
        row.operator("f1_material.import", text="Import XML")
        row.operator("f1_material.export", text="Export XML")
        layout.label(text=f"Library of SHADERINSTANCE")

        for block in scene.material_blocks:
            box = layout.box()
            box.prop(block, "shader_group")

            box.prop_search(block, "material_id", bpy.data, "materials", text="Material")
            select_op = box.operator("f1_material.select_material", text="Make Active", icon='CHECKMARK')
            select_op.material_name = block.material_id

            for tex_input in block.texture_inputs:
                row = box.row()
                row.prop(tex_input, "name", text=f"TextureID ({tex_input.parameter_id})")
                import_op = row.operator("f1_material.import_image", text="", icon="IMPORT", emboss=True)
                import_op.target_texture = tex_input.name

            box.operator("f1_material.remove_block", text="Remove Block")

        layout.operator("f1_material.add_block", text="Add Material Block")

# Operators
class ImportOperator(Operator):
    bl_idname = "f1_material.import"
    bl_label = "Import Material XML"

    filepath: StringProperty(subtype='FILE_PATH')

    def execute(self, context):
        global xml_tree_global
        try:
            xml_tree_global = ET.parse(self.filepath)
            self.report({'INFO'}, f"Succeed import from {self.filepath}")
            return {'FINISHED'}
        except Exception as e:
            self.report({'ERROR'}, f"Failed to import XML: {str(e)}")
            return {'CANCELLED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

class ExportOperator(Operator):
    bl_idname = "f1_material.export"
    bl_label = "Export Material XML"

    filepath: StringProperty(subtype='FILE_PATH')

    def execute(self, context):
        global xml_tree_global
        try:
            if not xml_tree_global:
                self.report({'ERROR'}, "No XML data to export")
                return {'CANCELLED'}

            root = xml_tree_global.getroot()
            library = root.find('./LIBRARY[@type="SHADERINSTANCE"]')
            if library is None:
                library = ET.SubElement(root, "LIBRARY", type="SHADERINSTANCE")

            for block in context.scene.material_blocks:
                block_data = SHADER_GROUP_BLOCKS[block.shader_group]
                shader_instance = ET.Element(
                    "SHADERINSTANCE",
                    shaderGroup=block.shader_group,
                    parameterCount=block_data["parameterCount"],
                    parameterSavedCount=block_data["parameterSavedCount"],
                    renderSortPriority=block_data["renderSortPriority"],
                    id=block.material_id
                )

                for input_data in block_data["inputs"]:
                    param_id = input_data[0]
                    tex_type = input_data[1]

                    shader_input = ET.Element(
                        "SHADERINPUT",
                        parameterID=param_id,
                        type=tex_type
                    )

                    if tex_type == "texture":
                        corresponding_input = next((ti for ti in block.texture_inputs if ti.parameter_id == param_id), None)
                        updated_texture = corresponding_input.name if corresponding_input else input_data[2]
                        shader_input.set("texture", updated_texture)
                    else:
                        shader_input.set("format", input_data[2])
                        shader_input.text = input_data[3]

                    shader_instance.append(shader_input)

                library.append(shader_instance)

            def indent(elem, level=0):
                i = "\n" + level * "    "
                if len(elem):
                    if not elem.text or not elem.text.strip():
                        elem.text = i + "    "
                    if not elem.tail or not elem.tail.strip():
                        elem.tail = i
                    for elem in elem:
                        indent(elem, level + 1)
                    if not elem.tail or not elem.tail.strip():
                        elem.tail = i
                else:
                    if level and (not elem.tail or not elem.tail.strip()):
                        elem.tail = i

            indent(root)

            buffer = BytesIO()
            xml_tree_global.write(buffer, encoding='utf-8', xml_declaration=True)
            with open(self.filepath, 'wb') as f:
                f.write(buffer.getvalue())

            self.report({'INFO'}, f"Succeed export to {self.filepath}")
            print("Export Succeed")
            return {'FINISHED'}
        except Exception as e:
            self.report({'ERROR'}, f"Failed to export XML: {str(e)}")
            return {'CANCELLED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

class ImportImageOperator(Operator):
    bl_idname = "f1_material.import_image"
    bl_label = "Import Already Image"

    filepath: StringProperty(subtype='FILE_PATH')
    target_texture: StringProperty()

    def execute(self, context):
        new_name = os.path.splitext(os.path.basename(self.filepath))[0]
        for block in context.scene.material_blocks:
            for tex_input in block.texture_inputs:
                if tex_input.name == self.target_texture:
                    tex_input.name = f"#{new_name}.tga"

        print(f"Updated texture name to '{new_name}'")
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

class SelectMaterialOperator(Operator):
    bl_idname = "f1_material.select_material"
    bl_label = "Select Active Material"
    material_name: StringProperty()

    def execute(self, context):
        if self.material_name in bpy.data.materials:
            context.object.active_material = bpy.data.materials[self.material_name]
            bpy.context.view_layer.objects.active.active_material = bpy.data.materials[self.material_name]
            self.report({'INFO'}, f"Material '{self.material_name}' selected")
        else:
            self.report({'ERROR'}, f"Material '{self.material_name}' not found!")
        return {'FINISHED'}

class AddBlockOperator(Operator):
    bl_idname = "f1_material.add_block"
    bl_label = "Add Material Block"

    def execute(self, context):
        block = context.scene.material_blocks.add()
        block.shader_group = "#env_diff.fx"
        block.material_id = "new_material"
        block.update_shader_group(context)

        return {'FINISHED'}

class RemoveBlockOperator(Operator):
    bl_idname = "f1_material.remove_block"
    bl_label = "Remove Material Block"

    def execute(self, context):
        scene = context.scene
        if scene.material_blocks:
            scene.material_blocks.remove(len(scene.material_blocks) - 1)
        return {'FINISHED'}

# Registration
def register():
    bpy.utils.register_class(TextureEntry)
    bpy.utils.register_class(MaterialBlock)
    bpy.types.Scene.material_blocks = CollectionProperty(type=MaterialBlock)
    bpy.utils.register_class(F1MaterialMakerPanel)
    bpy.utils.register_class(ImportOperator)
    bpy.utils.register_class(ExportOperator)
    bpy.utils.register_class(ImportImageOperator)
    bpy.utils.register_class(SelectMaterialOperator)
    bpy.utils.register_class(AddBlockOperator)
    bpy.utils.register_class(RemoveBlockOperator)

def unregister():
    bpy.utils.unregister_class(TextureEntry)
    bpy.utils.unregister_class(MaterialBlock)
    del bpy.types.Scene.material_blocks
    bpy.utils.unregister_class(F1MaterialMakerPanel)
    bpy.utils.unregister_class(ImportOperator)
    bpy.utils.unregister_class(ExportOperator)
    bpy.utils.unregister_class(ImportImageOperator)
    bpy.utils.unregister_class(SelectMaterialOperator)
    bpy.utils.unregister_class(AddBlockOperator)
    bpy.utils.unregister_class(RemoveBlockOperator)

if __name__ == "__main__":
    register()