import bpy
import os
import re
from bpy.props import StringProperty, CollectionProperty, EnumProperty, BoolProperty, IntProperty
from bpy.types import Operator, Panel, PropertyGroup, UIList
from difflib import SequenceMatcher

bl_info = {
    "name": "Improved Comprehensive Texture Assigner",
    "author": "Your Name",
    "version": (2, 2),
    "blender": (2, 80, 0),
    "location": "View3D > N-Panel > Texture Assigner",
    "description": "Automatically assign textures to materials with comprehensive naming detection and visual previews",
    "category": "Material",
}

# Expanded list of Principled BSDF inputs
texture_types = [
    ("base_color", "Base Color", ""),
    ("metallic", "Metallic", ""),
    ("specular", "Specular", ""),
    ("roughness", "Roughness", ""),
    ("normal", "Normal", ""),
    ("bump", "Bump", ""),
    ("displacement", "Displacement", ""),
    ("emission", "Emission", ""),
    ("alpha", "Alpha", ""),
    ("ambient_occlusion", "Ambient Occlusion", ""),
    ("subsurface", "Subsurface", ""),
    ("subsurface_color", "Subsurface Color", ""),
    ("clearcoat", "Clearcoat", ""),
    ("clearcoat_roughness", "Clearcoat Roughness", ""),
    ("transmission", "Transmission", ""),
    ("sheen", "Sheen", ""),
]

class SuggestedMaterial(PropertyGroup):
    name: StringProperty(name="Material Name", default="")

class TextureItem(PropertyGroup):
    path: StringProperty(name="Texture Path", default="")
    type: EnumProperty(items=texture_types, name="Texture Type")
    material: StringProperty(name="Material Name", default="")
    is_assigned: BoolProperty(name="Is Assigned", default=False)
    suggested_materials: CollectionProperty(type=SuggestedMaterial)

class TextureGroup(PropertyGroup):
    name: StringProperty(name="Group Name", default="")
    textures: CollectionProperty(type=TextureItem)

class TEXASSIGNER_UL_texture_list(UIList):

    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            row = layout.row(align=True)
            row.prop(item, "path", text="", emboss=False)
            row.prop(item, "type", text="", emboss=False)
            row.prop(item, "material", text="", emboss=False)
            row.prop(item, "is_assigned", text="", emboss=False)
            if item.suggested_materials:
                for mat in item.suggested_materials:
                    row.operator("texassigner.set_material", text=mat.name).material_name = mat.name

class TEXASSIGNER_UL_material_list(UIList):

    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            row = layout.row(align=True)
            row.label(text=item.name)

class TEXASSIGNER_OT_select_textures(Operator):
    bl_idname = "texassigner.select_textures"
    bl_label = "Select Textures"

    def execute(self, context):
        folder_path = bpy.path.abspath("//textures")
        for root, _, files in os.walk(folder_path):
            for file in files:
                if file.endswith((".png", ".jpg", ".jpeg")):
                    texture_path = os.path.join(root, file)
                    texture_name = os.path.basename(texture_path).split('.')[0]
                    texture_type = determine_texture_type(texture_name)
                    new_texture = context.scene.texassigner_groups[0].textures.add()
                    new_texture.path = texture_path
                    new_texture.type = texture_type
                    new_texture.is_assigned = False
        update_suggestions()
        return {'FINISHED'}

def determine_texture_type(texture_name):
    name = texture_name.lower()
    if "color" in name or "diffuse" in name:
        return 'base_color'
    elif "metallic" in name:
        return 'metallic'
    elif "specular" in name:
        return 'specular'
    elif "roughness" in name:
        return 'roughness'
    elif "normal" in name:
        return 'normal'
    elif "bump" in name:
        return 'bump'
    elif "displacement" in name:
        return 'displacement'
    elif "emission" in name:
        return 'emission'
    elif "alpha" in name:
        return 'alpha'
    elif "ao" in name or "ambient_occlusion" in name:
        return 'ambient_occlusion'
    elif "subsurface" in name and "color" in name:
        return 'subsurface_color'
    elif "subsurface" in name:
        return 'subsurface'
    elif "clearcoat" in name and "roughness" in name:
        return 'clearcoat_roughness'
    elif "clearcoat" in name:
        return 'clearcoat'
    elif "transmission" in name:
        return 'transmission'
    elif "sheen" in name:
        return 'sheen'
    else:
        return 'base_color'

class TEXASSIGNER_OT_assign_textures(Operator):
    bl_idname = "texassigner.assign_textures"
    bl_label = "Assign Textures"

    def execute(self, context):
        for group in context.scene.texassigner_groups:
            for item in group.textures:
                if item.material:
                    material = bpy.data.materials.get(item.material)
                    if material:
                        assign_texture_to_material(item.path, material, item.type)
                        item.is_assigned = True
        return {'FINISHED'}

def assign_texture_to_material(texture_path, material, texture_type):
    img = bpy.data.images.load(texture_path)
    tex = bpy.data.textures.new(name=os.path.basename(texture_path), type='IMAGE')
    tex.image = img
    if texture_type == 'base_color':
        material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = tex

class TEXASSIGNER_OT_preview_material(Operator):
    bl_idname = "texassigner.preview_material"
    bl_label = "Preview Material"

    group_index: IntProperty()

    def execute(self, context):
        group = context.scene.texassigner_groups[self.group_index]
        for item in group.textures:
            if item.material:
                preview_texture_on_model(item.path, item.material)
        return {'FINISHED'}

def preview_texture_on_model(texture_path, material_name):
    material = bpy.data.materials.get(material_name)
    if material:
        bpy.context.object.active_material = material
        img = bpy.data.images.load(texture_path)
        tex = bpy.data.textures.new(name=os.path.basename(texture_path), type='IMAGE')
        tex.image = img
        material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = tex

class TEXASSIGNER_OT_clear_assigned(Operator):
    bl_idname = "texassigner.clear_assigned"
    bl_label = "Clear Assigned"

    def execute(self, context):
        for group in context.scene.texassigner_groups:
            for item in group.textures:
                item.is_assigned = False
                item.material = ""
        return {'FINISHED'}

class TEXASSIGNER_PT_main_panel(Panel):
    bl_idname = "TEXASSIGNER_PT_main_panel"
    bl_label = "Texture Assigner"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Texture Assigner'

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

        layout.operator("texassigner.select_textures")
        layout.operator("texassigner.assign_textures")
        layout.operator("texassigner.clear_assigned")

        for idx, group in enumerate(scene.texassigner_groups):
            box = layout.box()
            row = box.row()
            row.label(text=group.name)
            row.operator("texassigner.preview_material", text="Preview").group_index = idx
            
            col = box.column()
            col.template_list("TEXASSIGNER_UL_texture_list", "", group, "textures", scene, "texassigner_active_texture_index")

        layout.label(text="Materials")
        layout.template_list("TEXASSIGNER_UL_material_list", "", bpy.data, "materials", scene, "texassigner_active_material_index")

class TEXASSIGNER_OT_set_material(Operator):
    bl_idname = "texassigner.set_material"
    bl_label = "Set Material"

    material_name: StringProperty()
    texture_item_index: IntProperty()
    group_index: IntProperty()

    def execute(self, context):
        group = context.scene.texassigner_groups[self.group_index]
        item = group.textures[self.texture_item_index]
        item.material = self.material_name
        return {'FINISHED'}

def get_suggested_materials(texture_name):
    suggested_materials = []
    for material in bpy.data.materials:
        similarity = SequenceMatcher(None, texture_name, material.name).ratio()
        if similarity > 0.7:  # Adjust similarity threshold as needed
            suggested_materials.append(material.name)
    return suggested_materials

def update_suggestions():
    for group in bpy.context.scene.texassigner_groups:
        for item in group.textures:
            texture_name = os.path.basename(item.path).split('.')[0]
            item.suggested_materials.clear()
            for mat_name in get_suggested_materials(texture_name):
                new_suggestion = item.suggested_materials.add()
                new_suggestion.name = mat_name

def preview_texture_on_model(texture_path, material_name):
    material = bpy.data.materials.get(material_name)
    if material:
        bpy.context.object.active_material = material
        img = bpy.data.images.load(texture_path)
        tex = bpy.data.textures.new(name=os.path.basename(texture_path), type='IMAGE')
        tex.image = img
        material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = tex

classes = (
    SuggestedMaterial,
    TextureItem,
    TextureGroup,
    TEXASSIGNER_UL_texture_list,
    TEXASSIGNER_UL_material_list,
    TEXASSIGNER_OT_select_textures,
    TEXASSIGNER_OT_assign_textures,
    TEXASSIGNER_OT_preview_material,
    TEXASSIGNER_OT_clear_assigned,
    TEXASSIGNER_PT_main_panel,
    TEXASSIGNER_OT_set_material,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.texassigner_groups = CollectionProperty(type=TextureGroup)
    bpy.types.Scene.texassigner_active_texture_index = IntProperty()
    bpy.types.Scene.texassigner_active_material_index = IntProperty()

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.texassigner_groups
    del bpy.types.Scene.texassigner_active_texture_index
    del bpy.types.Scene.texassigner_active_material_index

if __name__ == "__main__":
    register()
    if not bpy.context.scene.texassigner_groups:
        bpy.context.scene.texassigner_groups.add().name = "Group 1"
    update_suggestions()
Report: Error

bpy_struct "TextureItem" registration error: 'suggested_materials' CollectionProperty could not register (see previous error)

x