# ##### BEGIN GPL LICENSE BLOCK #####
#
#  Copyright (C) 2022 VFX Grace - All Rights Reserved
#
#  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 3 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, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####

import bpy
from bpy.props import BoolProperty, EnumProperty, IntProperty, FloatProperty, FloatVectorProperty, StringProperty
from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper
from bl_ui.properties_particle import particle_get_settings
import bl_ui.properties_texture as tex_ui
from .hair_modifier import _hair_modifier
from .utils import generate_unique_name, tag_view3d_redraw
from .props import HairModifierProps
from .modifier_manager import modifier_updated, block_update, UpdateType, HairModifierManager


class PARTICLE_OT_AddModifier(Operator):
    bl_idname = "particle.add_child_modifier"
    bl_label = "Add Modifier"
    bl_description = "Add modifier"
    bl_options = {'REGISTER', 'UNDO'}

    type: EnumProperty(name="Modifier Type", items=HairModifierProps.MODIFIER_TYPE_ITEMS)

    def execute(self, context: bpy.types.Context):
        part = particle_get_settings(context)
        modifiers = part.child_modifier.modifiers
        modifier = modifiers.add()
        modifier.type = self.type
        modifier.name = generate_unique_name(self.type.title(), ".", 1, modifiers)
        part.child_modifier.active_index = len(modifiers) - 1
        modifier_updated(context, part, UpdateType.ADD, self.type)
        tag_view3d_redraw(context)
        return {'FINISHED'}


class PARTICLE_OT_CopyModifier(Operator):
    bl_idname = "particle.copy_child_modifier"
    bl_label = "Copy Modifier"
    bl_description = "Copy modifier"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        part = particle_get_settings(context)
        return (part and part.child_type != 'NONE' and part.child_modifier.use_child_modifier
                and len(part.child_modifier.modifiers) > 0)

    def execute(self, context: bpy.types.Context):
        part = particle_get_settings(context)
        modifiers = part.child_modifier.modifiers
        modifier = modifiers.add()
        cur_md = modifiers[part.child_modifier.active_index]
        with block_update():
            cur_md.copy_props(modifier)
            part.child_modifier.active_index = len(modifiers) - 1
        modifier_updated(context, part, UpdateType.ADD, modifier.type)
        tag_view3d_redraw(context)
        return {'FINISHED'}


class PARTICLE_OT_RemoveModifier(Operator):
    bl_idname = "particle.remove_child_modifier"
    bl_label = "Remove Modifier"
    bl_description = "Remove selected modifier"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        part = particle_get_settings(context)
        return (part and part.child_type != 'NONE' and part.child_modifier.use_child_modifier
                and len(part.child_modifier.modifiers) > 0)

    def execute(self, context: bpy.types.Context):
        part = particle_get_settings(context)
        child_modifier = part.child_modifier
        index = child_modifier.active_index
        if len(child_modifier.modifiers) > index >= 0:
            md = child_modifier.modifiers[index]
            md.remove_curve_nodes()
            child_modifier.modifiers.remove(index)
            mds = len(child_modifier.modifiers)
            if mds and child_modifier.active_index >= mds:
                child_modifier.active_index = mds - 1
            modifier_updated(context, part, UpdateType.REMOVE, index)
            tag_view3d_redraw(context)
        return {'FINISHED'}


class PARTICLE_OT_MoveModifier(Operator):
    bl_idname = "particle.move_child_modifier"
    bl_label = "Move Modifier"
    bl_options = {'REGISTER', 'UNDO'}

    direction: EnumProperty(
        name="Move Direction",
        items=[('UP', "Move up", ""),
               ('DOWN', "Move down", "")],
        options={'HIDDEN'})

    @classmethod
    def description(cls, context, props):
        if props.direction == 'UP':
            return "Move Up"
        else:
            return "Move Down"

    @classmethod
    def poll(cls, context):
        part = particle_get_settings(context)
        return (part and part.child_type != 'NONE' and part.child_modifier.use_child_modifier
                and len(part.child_modifier.modifiers) > 1)

    def execute(self, context: bpy.types.Context):
        part = particle_get_settings(context)
        mods = part.child_modifier.modifiers
        index = part.child_modifier.active_index
        if self.direction == 'UP':
            if index <= 0:
                return {'CANCELLED'}
            mods.move(index, index - 1)
            part.child_modifier.active_index -= 1
        else:
            if index >= len(mods) - 1:
                return {'CANCELLED'}
            mods.move(index, index + 1)
            part.child_modifier.active_index += 1
        modifier_updated(context, part, UpdateType.MOVE, (index, part.child_modifier.active_index))
        tag_view3d_redraw(context)
        return {'FINISHED'}


class PARTICLE_OT_MakeCurveSingle(Operator):
    bl_idname = "particle.make_curve_single"
    bl_label = "Make Curve Single"
    bl_description = "Make curves of all modifiers single"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def _get_all_modifier_ntrees(cls, part, ntrees: set):
        for md in part.child_modifier.modifiers:
            md.get_curve_ntrees(ntrees)

    @classmethod
    def poll(cls, context):
        part = particle_get_settings(context)
        ntrees = set()
        cls._get_all_modifier_ntrees(part, ntrees)
        if not ntrees:
            return False
        other_ntrees = set()
        for settings in bpy.data.particles:
            if settings != part:
                cls._get_all_modifier_ntrees(settings, other_ntrees)
        if ntrees.intersection(other_ntrees):
            return True
        return False

    def execute(self, context: bpy.types.Context):
        part = particle_get_settings(context)
        ntrees = set()
        self._get_all_modifier_ntrees(part, ntrees)
        if not ntrees:
            return {'CANCELLED'}

        other_ntrees = set()
        for settings in bpy.data.particles:
            if settings != part:
                self._get_all_modifier_ntrees(settings, other_ntrees)

        ntree = None
        for nt in ntrees:
            if nt not in other_ntrees:
                ntree = nt
                break
        if ntree is None:
            ntree = bpy.data.node_groups.new(name=part.name, type='TextureNodeTree')
        for md in part.child_modifier.modifiers:
            md.reset_curves(ntree)
        return {'FINISHED'}


class PARTICLE_OT_AssignVertexGroup(Operator):
    bl_idname = "particle.assign_vertex_group"
    bl_label = "Assign Vertex Group"
    bl_description = "Assign a vertex group as mask"
    bl_property = "vertex_group"
    bl_options = {'REGISTER', 'UNDO'}
    vertex_groups = []

    def vertex_group_items(self, context):
        PARTICLE_OT_AssignVertexGroup.vertex_groups.clear()
        for i, vg in enumerate(context.object.vertex_groups):
            PARTICLE_OT_AssignVertexGroup.vertex_groups.append((vg.name, vg.name, "", i))
        return PARTICLE_OT_AssignVertexGroup.vertex_groups

    vertex_group: EnumProperty(name="Vertex Group Name", items=vertex_group_items, options={'SKIP_SAVE'})
    prop_path: StringProperty(name="Prop Path", options={'SKIP_SAVE'})

    def execute(self, context: bpy.types.Context):
        if not (self.vertex_group or self.prop_path):
            return {'CANCELLED'}

        try:
            prop = context.object.particle_systems.active.settings.path_resolve(self.prop_path, False)
            setattr(prop, "vertex_group", self.vertex_group)
            modifier_updated(context, prop.id_data, UpdateType.MODIFIED, None)
            tag_view3d_redraw(context)
        except ValueError:
            return {'CANCELLED'}
        return {'FINISHED'}

    def invoke(self, context, event):
        if not self.prop_path:
            return {'CANCELLED'}
        wm = context.window_manager
        if len(context.object.vertex_groups) == 0:
            popup = wm.popmenu_begin__internal("", icon='NONE')
            try:
                popup.layout.label(text="No vertex group in current object...")
            finally:
                wm.popmenu_end__internal(popup)
        else:
            wm.invoke_search_popup(self)
        return {"CANCELLED"}


class PARTICLE_OT_VertexGroupOperation(Operator):
    bl_idname = "prop.vertex_group_operate"
    bl_label = ""
    bl_options = {"REGISTER", "UNDO"}

    new_name: StringProperty(name="New Name")
    option: IntProperty(default=0, options={'HIDDEN', 'SKIP_SAVE'})

    @classmethod
    def poll(cls, context):
        obj = context.object
        return obj and obj.vertex_groups

    def invoke(self, context, event):
        self.origin_name = context.object.vertex_groups.active.name
        if self.option == 0:
            self.new_name = self.origin_name
            return context.window_manager.invoke_props_dialog(self)
        else:
            return self.execute(context)

    def execute(self, context):
        if self.option == 1:
            bpy.ops.object.vertex_group_remove(all=False, all_unlocked=False)
            self.rename_modifier_vertex_group(context, self.origin_name, '')
        elif self.origin_name != self.new_name.strip():
            context.object.vertex_groups.active.name = self.new_name.strip()
            name = context.object.vertex_groups.active.name
            self.rename_modifier_vertex_group(context, self.origin_name, name)
        return {'FINISHED'}

    def rename_modifier_vertex_group(self, context, oldname, name):
        for psys in context.object.particle_systems:
            with block_update():
                for mod in psys.settings.child_modifier.modifiers:
                    mod.rename_vertex_group(oldname, name)
            if self.option == 1:
                modifier_updated(context, psys.settings, UpdateType.MODIFIED, None)


class PARTICLE_OT_AddImage(Operator):
    bl_idname = "particle.add_tex_image"
    bl_label = "Add Texture Image"
    bl_description = "Add a image to texture"
    bl_options = {'REGISTER', 'UNDO'}

    tex_name: StringProperty(name="Texture Name", options={'SKIP_SAVE', 'HIDDEN'})

    # Image props
    name: StringProperty(
        name="Name",
        description="Image data-block name",
        default="Untitled",
        maxlen=64)

    width: IntProperty(
        name="Width",
        description="Image width",
        default=1024,
        min=1,
        soft_max=16384,
        subtype='PIXEL')

    height: IntProperty(
        name="Height",
        description="Image height",
        default=1024,
        min=1,
        soft_max=16384,
        subtype='PIXEL')

    color: FloatVectorProperty(
        name="Color",
        description="Default fill color",
        size=4,
        min=0.0,
        soft_max=1.0,
        default=[1.0, 1.0, 1.0, 1.0],
        subtype='COLOR_GAMMA')

    alpha: BoolProperty(
        name="Alpha",
        description="Create an image with an alpha channel",
        default=True)

    use_float: BoolProperty(
        name="32-bit Float",
        description="Create image with 32-bit floating-point bit depth",
        options={'SKIP_SAVE'})

    def execute(self, context):
        img = bpy.data.images.new(self.name,
                                  self.width,
                                  self.height,
                                  alpha=self.alpha,
                                  float_buffer=self.use_float,
                                  is_data=True)
        img.generated_color = self.color
        self.tex.image = img
        return {'FINISHED'}

    def invoke(self, context, event):
        if not self.tex_name or self.tex_name not in bpy.data.textures:
            return {'CANCELLED'}
        self.tex = bpy.data.textures[self.tex_name]
        return context.window_manager.invoke_props_dialog(self)

    def draw(self, context):
        lay = self.layout
        lay.use_property_split = True
        lay.use_property_decorate = False

        lay.prop(self, "name")
        lay.prop(self, "width")
        lay.prop(self, "height")
        lay.prop(self, "color")
        lay.prop(self, "alpha")
        lay.prop(self, "use_float")


class MocTexContext:
    def __init__(self, tex) -> None:
        self.tex = tex
        self.brush = None
        self.line_style = None
        self.particle_system = None

    @property
    def texture(self):
        return bpy.data.textures[self.tex.name]


def draw_texture_options(op, prop, ctx):
    lay = op.layout
    lay.template_ID(prop, "texture")
    if not prop.texture:
        return

    lay.separator()

    tex = prop.texture
    ctx.tex = tex
    split = lay.row().split(factor=0.2)
    split.label(text="Type")
    split.prop(tex, "type", text="")

    lay.template_preview(tex)

    if tex.type == 'IMAGE':
        if tex.image:
            lay.template_image(tex, "image", tex.image_user)
        else:
            row = lay.row(align=True)
            row.template_ID(tex, "image")
            row.operator("particle.add_tex_image", text="New", icon="ADD").tex_name = tex.name
    elif tex.type == 'BLEND':
        tex_ui.TEXTURE_PT_blend.draw(op, ctx)
    elif tex.type == 'STUCCI':
        tex_ui.TEXTURE_PT_stucci.draw(op, ctx)
    elif tex.type == 'CLOUDS':
        tex_ui.TEXTURE_PT_clouds.draw(op, ctx)
    elif tex.type == 'WOOD':
        tex_ui.TEXTURE_PT_wood.draw(op, ctx)
    elif tex.type == 'MARBLE':
        tex_ui.TEXTURE_PT_marble.draw(op, ctx)
    elif tex.type == 'MAGIC':
        tex_ui.TEXTURE_PT_magic.draw(op, ctx)
    elif tex.type == 'MUSGRAVE':
        tex_ui.TEXTURE_PT_musgrave.draw(op, ctx)
    elif tex.type == 'VORONOI':
        tex_ui.TEXTURE_PT_voronoi.draw(op, ctx)
    elif tex.type == 'DISTORTED_NOISE':
        tex_ui.TEXTURE_PT_distortednoise.draw(op, ctx)
    elif tex.type == 'VORONOI':
        tex_ui.TEXTURE_PT_voronoi.draw(op, ctx)

    lay.separator()


class PARTICLE_OT_EditTexture(Operator):
    bl_idname = "particle.edit_texture"
    bl_label = "Edit Texture"
    bl_description = "Edit texture properties"

    prop_path: StringProperty(name="Prop Path", options={'SKIP_SAVE', 'HIDDEN'})
    edit_image: BoolProperty(name="", options={'SKIP_SAVE', "HIDDEN"})

    def execute(self, context: bpy.types.Context):
        if not self.prop_path:
            return {'CANCELLED'}
        part = context.object.particle_systems.active.settings
        prop = part.path_resolve(self.prop_path, False)
        tex = prop.texture
        if tex and tex.type == 'IMAGE':
            bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
            context.tool_settings.image_paint.mode = 'IMAGE'
            context.tool_settings.image_paint.canvas = tex.image
        return {'FINISHED'}

    def invoke(self, context, event):
        if not self.prop_path:
            return {'CANCELLED'}
        self.part = context.object.particle_systems.active.settings
        self.prop = self.part.path_resolve(self.prop_path, False)
        self.moc_ctx = MocTexContext(self.prop.texture)
        if self.edit_image:
            return self.execute(context)
        return context.window_manager.invoke_props_dialog(self)

    def draw(self, context):
        draw_texture_options(self, self.prop, self.moc_ctx)


class PARTICLE_OT_AddTexture(Operator):
    bl_idname = "particle.add_texture"
    bl_label = "Add Texture"
    bl_description = "Add a texture to child modifier property"
    bl_options = {'REGISTER', 'UNDO'}

    prop_path: StringProperty(name="Prop Path", options={'SKIP_SAVE'})

    def execute(self, context: bpy.types.Context):
        bpy.ops.particle.edit_texture('EXEC_DEFAULT', prop_path=self.prop_path, edit_image=True)
        return {'FINISHED'}

    def cancel(self, context):
        self.prop.texture = None
        bpy.data.textures.remove(self.moc_ctx.tex)

    def invoke(self, context, event):
        if not self.prop_path:
            return {'CANCELLED'}
        self.tex = bpy.data.textures.new("Texture", 'IMAGE')
        self.part = context.object.particle_systems.active.settings
        self.prop = self.part.path_resolve(self.prop_path, False)
        setattr(self.prop, "texture", self.tex)
        self.moc_ctx = MocTexContext(self.tex)
        return context.window_manager.invoke_props_dialog(self)

    def draw(self, context):
        draw_texture_options(self, self.prop, self.moc_ctx)


class PARTICLE_OT_ExportABC(Operator, ExportHelper):
    bl_idname = "particle.hair_modifier_export_abc"
    bl_label = "Export ABC"
    bl_description = "Export ABC with ParticleSystem child modifier applied"
    bl_options = {'REGISTER'}

    filename_ext = ".abc"
    filter_glob: StringProperty(
        default="*.abc",
        options={'HIDDEN'},
    )

    start: IntProperty(
        name="Start Frame",
        description="Start frame of the export, use the default value to "
                    "take the start frame of the current scene",
    )
    end: IntProperty(
        name="End Frame",
        description="End frame of the export, use the default value to "
                    "take the end frame of the current scene",
    )
    xsamples: IntProperty(
        name="Transform Samples",
        description="Number of times per frame transformations are sampled",
        min=1, max=128,
        default=1,
    )
    gsamples: IntProperty(
        name="Geometry Samples",
        description="Number of times per frame object data are sampled",
        min=1, max=128,
        default=1,
    )
    sh_open: FloatProperty(
        name="Shutter Open",
        description="Time at which the shutter is open",
        min=-1, max=1,
        precision=3,
        step=1,
        default=0,
    )
    sh_close: FloatProperty(
        name="Shutter Close",
        description="Time at which the shutter is closed",
        min=-1, max=1,
        precision=3,
        step=1,
        default=1,
    )
    use_instancing: BoolProperty(
        name="Use Instancing",
        description="Export data of duplicated objects as Alembic instances; speeds up the export "
                    "and can be disabled for compatibility with other software",
        default=True,
    )
    export_custom_properties: BoolProperty(
        name="Export Custom Properties",
        description="Export custom properties to Alembic .userProperties",
        default=True,
    )

    evaluation_mode: EnumProperty(
        name="Use Settings for",
        items=[('RENDER', "Render", "Use Render settings for object visibility, modifier settings, etc", 0),
               ('VIEWPORT', "Viewport", "Use Viewport settings for object visibility, modifier settings, etc", 1)],
        description="Determines visibility of objects, modifier settings, and other areas where there "
                    "are different settings for viewport and rendering"
    )

    selected: BoolProperty(
        name="Selected Objects Only",
        description="Export only selected objects",
        default=False,
    )
    renderable_only: BoolProperty(
        name="Renderable Objects Only",
        description="Export only objects marked renderable in the outliner",
        default=True,
    )
    visible_objects_only: BoolProperty(
        name="Visible Objects Only",
        description="Export only objects that are visible",
        default=False,
    )
    flatten: BoolProperty(
        name="Flatten Hierarchy",
        description="Do not preserve objects' parent/children relationship",
        default=False,
    )
    uvs: BoolProperty(
        name="UVs",
        description="Export UVs",
        default=True,
    )
    packuv: BoolProperty(
        name="Pack UV Islands",
        description="Export UVs with packed island",
        default=True,
    )
    normals: BoolProperty(
        name="Normals",
        description="Export normals",
        default=True,
    )
    vcolors: BoolProperty(
        name="Vertex Colors",
        description="Export vertex colors",
        default=False,
    )
    face_sets: BoolProperty(
        name="Face Sets",
        description="Export per face shading group assignments",
        default=True,
    )
    subdiv_schema: BoolProperty(
        name="Use Subdivision Schema",
        description="Export meshes using Alembic's subdivision schema",
        default=False,
    )
    apply_subdiv: BoolProperty(
        name="Apply Subdivision Surface",
        description="Export subdivision surfaces as meshes",
        default=False,
    )
    curves_as_mesh: BoolProperty(
        name="Curves as Mesh",
        description="Export curves and NURBS surfaces as meshes",
        default=False,
    )
    global_scale: FloatProperty(
        name="Scale",
        description="Value by which to enlarge or shrink the objects with respect to the world's origin",
        min=0.0001, max=1000,
        default=1,
        precision=3,
        step=1,
    )
    triangulate: BoolProperty(
        name="Triangulate",
        description="Export polygons (quads and n-gons) as triangles",
        default=False,
    )
    enum_items = [
        ("BEAUTY", "Beauty", "Split the quads in nice triangles, slower method"),
        ("FIXED", "Fixed", "Split the quads on the first and third vertices"),
        ("FIXED_ALTERNATE", "Fixed Alternate", "Split the quads on the 2nd and 4th vertices"),
        ("SHORTEST_DIAGONAL", "Shortest Diagonal", "Split the quads based on the distance between the vertices"),
    ]
    quad_method: EnumProperty(
        items=enum_items,
        name="Quad Method",
        description="Method for splitting the quads into triangles",
        default="SHORTEST_DIAGONAL",
    )
    ngon_method: EnumProperty(
        items=enum_items,
        name="N-gon Method",
        description="Method for splitting the n-gons into triangles",
        default="BEAUTY"
    )
    export_hair: BoolProperty(
        name="Export Hair",
        description="Exports hair particle systems as animated curves",
        default=True,
    )
    export_particles: BoolProperty(
        name="Export Particles",
        description="Exports non-hair particle systems",
        default=True,
    )

    def execute(self, context):
        import time
        ignore_args = ["filter_glob"]
        if bpy.app.version <= (2, 93, 1):
            ignore_args.append("evaluation_mode")
        else:
            ignore_args.append("renderable_only")
        keywords = self.as_keywords(ignore=tuple(ignore_args))
        HairModifierManager.is_exporting_abc = True
        _hair_modifier.set_scene_cddata_masks_to_mesh(context.scene.as_pointer())
        bpy.ops.wm.alembic_export(**keywords, as_background_job=True)
        time.sleep(1)
        return {'FINISHED'}

    def invoke(self, context, event):
        self.start = context.scene.frame_start
        self.end = context.scene.frame_end
        return super().invoke(context, event)

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True
        layout.use_property_decorate = False

        box = layout.box()
        box.label(text="Manual Transform")
        box.prop(self, "global_scale")

        box = layout.box()
        box.label(text="Scene Options", icon="SCENE_DATA")
        col = box.column()
        sub = col.column(align=True)
        sub.prop(self, "start")
        sub.prop(self, "end")
        col.prop(self, "xsamples")
        col.prop(self, "gsamples")
        sub = col.column(align=True)
        sub.prop(self, "sh_open", slider=True)
        sub.prop(self, "sh_close", slider=True)
        sub.prop(self, "use_instancing")
        sub.prop(self, "export_custom_properties")

        col.separator()
        col.prop(self, "flatten")

        sub = col.column(align=True, heading="Only")
        sub.prop(self, "selected", text="Selected Objects")
        if bpy.app.version <= (2, 93, 1):
            sub.prop(self, "renderable_only", text="Renderable Objects")
        sub.prop(self, "visible_objects_only", text="Visible Objects")

        if bpy.app.version >= (2, 93, 2):
            col = box.column(align=True)
            col.prop(self, "evaluation_mode")

        box = layout.box()
        box.row().label(text="Object Options", icon="OBJECT_DATA")
        col = box.column()
        col.prop(self, "uvs")
        row = col.row()
        row.prop(self, "packuv")
        row.active = self.uvs
        col.prop(self, "normals")
        col.prop(self, "vcolors")
        col.prop(self, "face_sets")
        col.prop(self, "curves_as_mesh")

        col.separator()
        sub = col.column(align=True, heading="Subdivisions")
        sub.prop(self, "apply_subdiv", text="Apply")
        sub.prop(self, "subdiv_schema", text="Use Schema")

        col.separator()
        col.prop(self, "triangulate")
        sub = col.column()
        sub.prop(self, "quad_method")
        sub.prop(self, "ngon_method")
        sub.enabled = self.triangulate

        box = layout.box()
        box.label(text="Particle Systems", icon="PARTICLES")
        col = box.column(align=True)
        col.prop(self, "export_hair")
        col.prop(self, "export_particles")


def menu_func_export_abc(self, context):
    self.layout.operator(PARTICLE_OT_ExportABC.bl_idname, text="Alembic HairModifier(.abc)")


classes = [
    PARTICLE_OT_AddModifier, PARTICLE_OT_CopyModifier, PARTICLE_OT_MoveModifier,
    PARTICLE_OT_RemoveModifier, PARTICLE_OT_MakeCurveSingle, PARTICLE_OT_AssignVertexGroup,
    PARTICLE_OT_AddTexture, PARTICLE_OT_AddImage, PARTICLE_OT_EditTexture, PARTICLE_OT_ExportABC,
    PARTICLE_OT_VertexGroupOperation,
]


def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    bpy.types.TOPBAR_MT_file_export.append(menu_func_export_abc)


def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)

    bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_abc)
