bl_info = {
    "name": "Bevel After Boolean",
    "author": "Rodinkov Ilya",
    "version": (0, 3, 0, 1),
    "blender": (2, 90, 0),
    "location": "View3D > Tools > Boolean Bevel > Bevel",
    "description": "Create bevel after boolean",
    "warning": "This add-on is still in development.",
    "wiki_url": "",
    "category": "Object",
}

import time
import numpy as np
import math
import mathutils
DEBUG = True

import bpy
from bpy.types import (
        Operator,
    Panel, PropertyGroup
)

from bpy.props import *



class BAB_Props(PropertyGroup):
    # Other
    offset: FloatProperty(name="Bevel Radius", default=0.05, min=0.00002, max=1000.0, step=1, subtype='DISTANCE')
    stop_calc: BoolProperty(name="Stop calculations", default=False)
    wire: BoolProperty(name="Wire", default=False)
    create_slice: BoolProperty(name="Slice", default=False)

    apply_all: BoolProperty(name="Skip Boolean", default=True)

    remove_all: BoolProperty(name="Skip Boolean", default=True)

    hide: BoolProperty(name="Show modifiers", default=True)

    show_render: BoolProperty(name="Show in render", default=False)
    display_as: EnumProperty(name="Display as",
                                      items=(("BOUNDS", "Bounds", "On visibility"),
                                             ("WIRE", "Wire", "Off visibility"),
                                             ("SOLID", "Solid", "Off visibility"),
                                             ("TEXTURED", "Textured", "Off visibility"),
                                             ),
                                      description="Display as",
                                      default="TEXTURED")

    axis: EnumProperty(name="Axis",
                            items=(("X", "X", "Use x axis"),
                                    ("Y", "Y", "Use y axis"),
                                    ("Z", "Z", "Use z axis"),
                                    ),
    description="Axis",
    default="X")


    flip_direction: BoolProperty(name="Flip Direction", default=False)

    merge_threshold: FloatProperty(name="Merge Dist", default=0.001, min=0.0, max=1.0, step=1, subtype='DISTANCE')

    pipe_radius: FloatProperty(name="Pipe Radius", default=0.05, min=0.00002, max=1000.0, step=1, subtype='DISTANCE')
    pipe_wire: BoolProperty(name="Wire", default=False)
    pipe_stop: BoolProperty(name="Stop calculations", default=False)

class BAB_PT_Panel(Panel):
    """Main Panel of add-on"""
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "BaB"
    bl_label = "Bevel After Boolean "

    def draw(self, context):
        bab_props = context.window_manager.bab_props
        layout = self.layout
        box = layout.box()
        box.label(text="Bevel:")
        box.operator("bab.bevel")
        box.prop(bab_props, "offset")
        row = box.row(align=True)
        row.alignment = 'LEFT'
        row.prop(bab_props, "wire")
        row.prop(bab_props, "stop_calc")
        box.prop(bab_props, "create_slice")



        #

        box = layout.box()
        box.label(text="New objects:")

        # box.layout.separator(factor=1.0)
        box.prop(bab_props, "pipe_radius")
        box.operator("bab.create_pipe")
        row = box.row(align=True)
        row.alignment = 'LEFT'
        row.prop(bab_props, "pipe_wire")
        row.prop(bab_props, "pipe_stop")
        box.operator("bab.slice")


        box = layout.box()
        box.label(text="Modifiers:")
        box.alignment = 'CENTER'
        box.operator("bab.apply_modifiers")
        box.prop(bab_props, "apply_all")
        box.operator("bab.remove_modifiers")
        box.prop(bab_props, "remove_all")
        box.operator("bab.hide_modifiers")
        box.prop(bab_props, "hide")

        box = layout.box()
        box.label(text="Visibility:")
        box.prop(bab_props, "show_render")
        box.prop(bab_props, "display_as")
        box.operator("bab.visibility")

        box = layout.box()
        box.label(text="Symmetrize:")
        box.operator("bab.symmetrize")
        box.prop(bab_props, "axis")
        box.prop(bab_props, "flip_direction")
        box.prop(bab_props, "merge_threshold")

class BAB_OP_Bevel(Operator):
    """Create bevel on object"""
    bl_idname = "bab.bevel"
    bl_label = "Bevel"
    bl_options = {'REGISTER', 'UNDO', 'PRESET'}

    @classmethod
    def poll(cls, context):
        return context.active_object

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        row = box.row(align=True)
        row.alignment = 'CENTER'
        row.prop(self, "wire")
        row.prop(self, "preview_curve")
        row.prop(self, "stop_calc")

        row = box.row(align=True)
        row.alignment = 'CENTER'
        row.prop(self, "save_settings")
        if not self.edit_mode:
            row.prop(self, "create_slice")

        box.label(text="Basic Parameters:")
        if not self.edit_mode:
            box.prop(self, "operation")
        box.prop(self, "offset")

        if not self.edit_mode:
            box = layout.box()
            box.label(text="Subsurf:")
            column = box.column()
            if self.src_subsrf_index == -1:
                column.enabled = False
            column.prop(self, "subdiv_a", text=self.obj_name)
            column = box.column()
            if self.bool_subsrf_index == -1:
                column.enabled = False
            column.prop(self, "subdiv_b", text=self.bool_name)

        box = layout.box()
        box.label(text="Bevel:")
        box.prop(self, "bevel_profile")
        box.prop(self, "bevel_segments")
        box.prop(self, "corner")
        box.prop(self, "fix_outside")
        box.prop(self, "abs_size")
        if self.abs_size:
            box.prop(self, "corner_dist")
        else:
            box.prop(self, "corner_size")
        box = layout.box()

        box.label(text="Normals:")
        box.prop(self, "transfer")
        box.prop(self, "transfer_factor")

        box.prop(self, "triangulate")

        if self.triangulate:
            box.prop(self, "method")

        box = layout.box()
        box.label(text="Patch Parameters:")
        box.prop(self, "smooth")
        box.prop(self, "factor")
        box.prop(self, "remove_doubles")
        box.prop(self, "subdivide")
        box.prop(self, "simplify")

        box = layout.box()
        box.label(text="Refine:")
        box.prop(self, "refine")

        box.prop(self, "refine_type")
        if self.refine_type == "DIST":
            box.prop(self, "refine_dist")

        if self.refine_type == "COUNT":
            box.prop(self, "refine_count")

        box.prop(self, "refine_multiply")
        box.prop(self, "refine_shift")
        box.prop(self, "refine_accuracy")


        box = layout.box()
        box.label(text="Pipe settings:")
        box.prop(self, "refine_res")
        box.prop(self, "mean_tilt")
        box.prop(self, "twist_smooth")
        # box.prop(self, "pipe_smooth")


    def invoke(self, context, event):
        if DEBUG:
            time_start = time.time()

        # Меняем значения
        bab_props = context.window_manager.bab_props
        self.stop_calc = bab_props.stop_calc
        self.wire = bab_props.wire
        self.offset = bab_props.offset
        self.create_slice = bab_props.create_slice

        self.wire_pre = bab_props.wire
        self.offset_pre = bab_props.offset
        self.create_slice_pre = bab_props.create_slice

        # исходный объект
        src_obj = context.active_object
        # передаем имя объекта в props
        self.obj_name =src_obj.name
        # Находим индекс boolean'а
        self.bool_index = get_boolean_index(src_obj)

        self.edit_mode = src_obj.data.is_editmode

        if not self.edit_mode:
            # Проверяем что boolean добавлен к объекту
            if self.bool_index == -1:
                self.report({'ERROR'}, "Boolean not found")
                return {'CANCELLED'}
            # объект boolean
            bool_obj = src_obj.modifiers[self.bool_index].object
            # Проверяем, что boolean не пустой
            if not bool_obj:
                self.report({'ERROR'}, "Boolean disabled")
                return {'CANCELLED'}
            # Передаем имя boolean в props
            self.bool_name = bool_obj.name
            # Передаем в props операцию boolean
            self.operation = src_obj.modifiers[self.bool_index].operation
            # ищем subsurf у объекта
            self.src_subsrf_index = get_subsurf_index(src_obj)
            # меняем значение props на нужное
            if self.src_subsrf_index > -1:
                self.subdiv_a = src_obj.modifiers[self.src_subsrf_index].levels
            else:
                self.subdiv_a = 0
            # ищем subsurf у boolean
            self.bool_subsrf_index = get_subsurf_index(bool_obj)
            # меняем значение props на нужное
            if self.bool_subsrf_index > -1:
                self.subdiv_b = bool_obj.modifiers[self.bool_subsrf_index].levels
            else:
                self.subdiv_b = 0
            # запускаем главный цикл

        if DEBUG:
            print("invoke: %.4f sec\n" % (time.time() - time_start))
        return self.execute(context)


    # boolean operation
    operation: EnumProperty(name="Operation",
                                      items=(("UNION", "Union", "Use Union"),
                                             ("DIFFERENCE", "Difference", "Use Difference"),
                                             ("INTERSECT", "Intersect", "Use Intersect"),
                                             ),
                                      description="Boolean Operation",
                                      default="UNION")

    create_slice: BoolProperty(name="Slice", default=False)

    stop_calc : BoolProperty(name="Stop", default=False)
    wire: BoolProperty(name="Wire", default=False)
    preview_curve: BoolProperty(name="Curve", default=False)

    offset: FloatProperty(name="Bevel Radius", default=0.05, min=0.00002, max=1000.0, step=1, subtype='DISTANCE')
    twist_smooth: IntProperty(name="Pipe Twist Smooth", default=64, min=0, max=5000)
    mean_tilt: FloatProperty(name="Mean Tilt", default=math.pi/4, min=-6.28, max=6.28, step=10, subtype='ANGLE')

    # refine
    refine_accuracy: IntProperty(name="Accuracy", default=64, min=1, max=1024)
    refine: BoolProperty(name="Refine", default=True)
    refine_res: IntProperty(name="Pipe Segments", default=4, min=0, max=30)
    refine_type: EnumProperty(name="Refine Type",
                                      items=(("DIST", "Dist", "Use Distantion"),
                                             ("COUNT", "Count", "Use Conunt"),
                                             ("AUTO", "Original", "Use Original Conunts"),
                                             ),
                                      description="Refine Type",
                                      default="AUTO")

    refine_count: IntProperty(name="Count", default=50, min=4, max=5000)
    refine_dist: FloatProperty(name="Dist", default=0.08, min=0.001, max=1000.0, subtype='DISTANCE')
    refine_shift: FloatProperty(name="Shift", default=0.0, min=-1000, max=1000.0, subtype='DISTANCE')
    refine_multiply: FloatProperty(name="Multiply", default=1.0, min=0.001, max=1000.0)

    # bevel
    bevel_profile: FloatProperty(name="Bevel Profile", default=0.5, min=0, max=1.0)
    bevel_segments: IntProperty(name="Bevel Segments", default=10, min=0, max=2000)
    bevel_corner: FloatProperty(name="Bevel Corner", default=1.0, min=0, max=1)
    corner: FloatProperty(name="Corner Profile", default=1.0, min=0.0, max=1.0)
    abs_size: BoolProperty(name="Absolute size", default=False)
    corner_size: FloatProperty(name="Corner Size", default=1.0, min=0.001, max=1.0, step=10)
    corner_dist: FloatProperty(name="Corner Radius", default=0.05, min=0.001, max=1000.0, step=1, subtype='DISTANCE')
    fix_outside: BoolProperty(name="Fix Outside Corner", default=True)

    # patch
    remove_doubles: FloatProperty(name="Remove Doubles", default=0.001, min=0.0, max=1.0, step=1, subtype='DISTANCE')
    subdivide: IntProperty(name="Subdivide Patch", default=0, min=0, max=5000)
    smooth: IntProperty(name="Smooth Patch", default=0, min=0, max=5000)
    factor: FloatProperty(name="Smooth Factor", default=0.5, min=0.0, max=1.0, step=1)
    simplify: FloatProperty(name="Simplify", default=0.0, min=0.0, max=180.0, step=1, subtype="ANGLE")

    # сохранение настроек
    save_settings: BoolProperty(name="Save settings", default=True)

    # Subsurf
    subdiv_a: IntProperty(name="Subdiv A", default=0, min=0, max=6)
    subdiv_b: IntProperty(name="Subdiv B", default=0, min=0, max=6)

    # data_transfer
    transfer: BoolProperty(name="Transfer Normals", default=True)
    transfer_factor: FloatProperty(name="Transfer Factor", default=1.0, min=0.0, max=1.0, step=1)
    triangulate: BoolProperty(name="Split NGon", default=False)
    method: EnumProperty(name="Method",
                                    items=(("BEAUTY", "BEAUTY", "Use BEAUTY"),
                                           ("CLIP", "CLIP", "Use CLIP")),
                                    description="Method for splitting the polygons into triangles",
                                    default="BEAUTY")

    wire_pre = False
    create_slice_pre = False
    offset_pre = 0.05

    obj_name = ""
    bool_name = ""
    bool_index = -1
    src_subsrf_index = -1
    bool_subsrf_index = -1
    edit_mode = False


    def execute(self, context):
        # исходный объект
        src_obj = context.active_object
        # bool_obj = src_obj.modifiers[self.bool_index].object

        # Включаем и выключаем сетку
        src_obj.show_wire = self.wire
        src_obj.show_all_edges = self.wire
        # останавливаем выполнение скрипта
        if self.stop_calc:
            return {'FINISHED'}

        if not self.edit_mode:
            # подготавливаем объект и получаем curve на местах пересечения
            curve, bool_obj, transfer_obj, slice_obj  = prepare_in_obj(self, context, src_obj)
        else:
            curve, bool_obj, transfer_obj = prepare_in_edit(self, context, src_obj)

        for spline in curve.data.splines:
            pipe, guide = create_pipe(self, context, curve)
            # Удаляем сплайн
            curve.data.splines.remove(spline)
            inside_vertices = find_inside(pipe, guide, self.offset)

            fail = fix_corner(self, pipe, inside_vertices, sides=4 + 2*self.refine_res)

            if self.preview_curve or fail:
                pipe.show_wire = True
                pipe.show_all_edges = True
                pipe.show_in_front = True
                guide.display_type = 'BOUNDS'
                curve.display_type = 'BOUNDS'
                pipe.select_set(True)
                bpy.ops.object.shade_flat()
                if fail:
                    self.report({'INFO'}, "self-Intersect")
                return {'FINISHED'}

            # Применяем boolean
            do_boolean(context, pipe, src_obj)
            # Выравниваем вершины
            align(src_obj, pipe, guide, sides=4 + 2*self.refine_res)



            if self.create_slice:
                # bpy.ops.object.mode_set(mode='OBJECT')
                # Применяем boolean
                context.view_layer.objects.active = slice_obj
                bpy.ops.object.mode_set(mode='OBJECT')
                # return {"FINISHED"}
                do_boolean(context, pipe, slice_obj)
                # Выравниваем вершины
                align(slice_obj, pipe, guide, sides=4 + 2*self.refine_res)
                context.view_layer.objects.active = src_obj



            pipe.select_set(True)
            guide.select_set(True)
            bpy.ops.object.delete(use_global=False)

        bpy.ops.object.mode_set(mode='EDIT')
        # Режим вершин
        bpy.ops.mesh.select_mode(type='VERT')
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.select_less(use_face_step=True)
        bpy.ops.object.vertex_group_assign()

        bpy.ops.object.vertex_group_select()
        bpy.ops.mesh.remove_doubles(threshold=0.0, use_unselected=False)

        # чистим от лишних ребер и точек
        bpy.ops.mesh.select_more(use_face_step=False)
        bpy.ops.mesh.select_all(action='INVERT')
        # удаляем лишние вершины
        bpy.ops.mesh.edge_collapse()
        bpy.ops.mesh.dissolve_verts(use_face_split=True, use_boundary_tear=False)


        bpy.ops.mesh.select_mode(type='EDGE')
        bpy.ops.object.vertex_group_select()
        bpy.ops.mesh.select_more(use_face_step=True)
        bpy.ops.mesh.hide(unselected=True)

        bpy.ops.mesh.select_face_by_sides(number=3, type='EQUAL', extend=False)
        bpy.ops.object.vertex_group_deselect()
        bpy.ops.mesh.edge_collapse()

        bpy.ops.mesh.reveal()

        # выворачиваем нормали
        # bpy.ops.mesh.select_all(action='SELECT')
        # bpy.ops.mesh.normals_make_consistent(inside=False)

        bpy.ops.mesh.select_all(action='DESELECT')

        if self.transfer:
            bpy.context.tool_settings.vertex_group_weight = self.transfer_factor
            src_obj.vertex_groups.active_index = 0
            bpy.ops.object.vertex_group_select()
            bpy.ops.mesh.select_more(use_face_step=True)
            bpy.ops.object.vertex_group_assign()
            bpy.ops.mesh.select_all(action='DESELECT')

            src_obj.vertex_groups.active_index = 1
            bpy.ops.object.vertex_group_select()
            bpy.ops.mesh.select_more(use_face_step=True)
            bpy.ops.object.vertex_group_assign()

            bpy.context.tool_settings.vertex_group_weight = 1.0

            bpy.ops.object.mode_set(mode='OBJECT')

            data_transfer_modifier = src_obj.modifiers.new(name="Boolean Bevel Custom Normals", type="DATA_TRANSFER")
            data_transfer_modifier.show_viewport = False
            data_transfer_modifier.object = transfer_obj
            data_transfer_modifier.use_loop_data = True
            data_transfer_modifier.data_types_loops = {"CUSTOM_NORMAL"}
            data_transfer_modifier.loop_mapping = "POLYINTERP_NEAREST"
            data_transfer_modifier.vertex_group = src_obj.vertex_groups[0].name

            apply_name_1 = data_transfer_modifier.name


            data_transfer_modifier = src_obj.modifiers.new(name="Boolean Bevel Custom Normals", type="DATA_TRANSFER")
            data_transfer_modifier.show_viewport = False
            data_transfer_modifier.object = bool_obj
            data_transfer_modifier.use_loop_data = True
            data_transfer_modifier.data_types_loops = {"CUSTOM_NORMAL"}
            data_transfer_modifier.loop_mapping = "POLYINTERP_NEAREST"
            data_transfer_modifier.vertex_group = src_obj.vertex_groups[1].name

            apply_name_2 = data_transfer_modifier.name

            src_obj.vertex_groups.active_index = 2

        if self.triangulate:
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.object.vertex_group_select()
            bpy.ops.mesh.select_more(use_face_step=True)
            bpy.ops.mesh.select_more(use_face_step=True)
            bpy.ops.object.vertex_group_deselect()
            bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method=self.method)
            bpy.ops.mesh.select_all(action='DESELECT')

        do_bevel(self)
        bpy.ops.object.mode_set(mode='OBJECT')

        if self.create_slice:
            context.view_layer.objects.active = slice_obj
            bpy.ops.object.mode_set(mode='EDIT')
            # Режим вершин
            bpy.ops.mesh.select_mode(type='VERT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.select_less(use_face_step=True)
            bpy.ops.object.vertex_group_assign()

            bpy.ops.object.vertex_group_select()
            bpy.ops.mesh.remove_doubles(threshold=0.0, use_unselected=False)

            # чистим от лишних ребер и точек
            bpy.ops.mesh.select_more(use_face_step=False)
            bpy.ops.mesh.select_all(action='INVERT')
            # удаляем лишние вершины
            bpy.ops.mesh.edge_collapse()
            bpy.ops.mesh.dissolve_verts(use_face_split=True, use_boundary_tear=False)


            bpy.ops.mesh.select_mode(type='EDGE')
            bpy.ops.object.vertex_group_select()
            bpy.ops.mesh.select_more(use_face_step=True)
            bpy.ops.mesh.hide(unselected=True)

            bpy.ops.mesh.select_face_by_sides(number=3, type='EQUAL', extend=False)
            bpy.ops.object.vertex_group_deselect()
            bpy.ops.mesh.edge_collapse()

            bpy.ops.mesh.reveal()

            # выворачиваем нормали
            # bpy.ops.mesh.select_all(action='SELECT')
            # bpy.ops.mesh.normals_make_consistent(inside=False)

            bpy.ops.mesh.select_all(action='DESELECT')

            if self.transfer:
                bpy.context.tool_settings.vertex_group_weight = self.transfer_factor
                slice_obj.vertex_groups.active_index = 0
                bpy.ops.object.vertex_group_select()
                bpy.ops.mesh.select_more(use_face_step=True)
                bpy.ops.object.vertex_group_assign()
                bpy.ops.mesh.select_all(action='DESELECT')

                slice_obj.vertex_groups.active_index = 1
                bpy.ops.object.vertex_group_select()
                bpy.ops.mesh.select_more(use_face_step=True)
                bpy.ops.object.vertex_group_assign()

                bpy.context.tool_settings.vertex_group_weight = 1.0

                bpy.ops.object.mode_set(mode='OBJECT')

                data_transfer_modifier = slice_obj.modifiers.new(name="Boolean Bevel Custom Normals", type="DATA_TRANSFER")
                data_transfer_modifier.show_viewport = False
                data_transfer_modifier.object = transfer_obj
                data_transfer_modifier.use_loop_data = True
                data_transfer_modifier.data_types_loops = {"CUSTOM_NORMAL"}
                data_transfer_modifier.loop_mapping = "POLYINTERP_NEAREST"
                data_transfer_modifier.vertex_group = slice_obj.vertex_groups[0].name

                apply_name_1 = data_transfer_modifier.name


                data_transfer_modifier = slice_obj.modifiers.new(name="Boolean Bevel Custom Normals", type="DATA_TRANSFER")
                data_transfer_modifier.show_viewport = False
                data_transfer_modifier.object = bool_obj
                data_transfer_modifier.use_loop_data = True
                data_transfer_modifier.data_types_loops = {"CUSTOM_NORMAL"}
                data_transfer_modifier.loop_mapping = "POLYINTERP_NEAREST"
                data_transfer_modifier.vertex_group = slice_obj.vertex_groups[1].name

                apply_name_2 = data_transfer_modifier.name

                slice_obj.vertex_groups.active_index = 2

            if self.triangulate:
                bpy.ops.object.mode_set(mode='EDIT')
                bpy.ops.mesh.select_all(action='DESELECT')
                bpy.ops.object.vertex_group_select()
                bpy.ops.mesh.select_more(use_face_step=True)
                bpy.ops.mesh.select_more(use_face_step=True)
                bpy.ops.object.vertex_group_deselect()
                bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method=self.method)
                bpy.ops.mesh.select_all(action='DESELECT')

            do_bevel(self)

        # context.view_layer.objects.active = src_obj
        bpy.ops.object.mode_set(mode='OBJECT')
        context.view_layer.objects.active = src_obj

        if self.transfer:
            if self.operation == "DIFFERENCE":
                vert = bool_obj.data
                vert.flip_normals()
                vert.update()

            bpy.ops.object.modifier_apply(modifier=apply_name_1)
            bpy.ops.object.modifier_apply(modifier=apply_name_2)

            # apply_modifiers(src_obj.modifiers, src_obj)



            if self.operation == "DIFFERENCE":
                vert = bool_obj.data
                vert.flip_normals()
                vert.update()
            if self.create_slice:
                context.view_layer.objects.active = slice_obj
                # включаем автосглаживание
                context.object.data.use_auto_smooth = True
                # угол авто скглаживания
                context.object.data.auto_smooth_angle = 3.14159
                bpy.ops.object.modifier_apply(modifier=apply_name_1)
                bpy.ops.object.modifier_apply(modifier=apply_name_2)
                context.view_layer.objects.active = src_obj
            bpy.data.objects['BAB_TRANSFER'].select_set(True)


        curve.select_set(True)

        bpy.ops.object.delete(use_global=False)


        bab_props = context.window_manager.bab_props
        if self.save_settings:
            bab_props.wire = self.wire
            bab_props.offset = self.offset
            bab_props.create_slice = self.create_slice
        else:
            bab_props.wire = self.wire_pre
            bab_props.offset = self.offset_pre
            bab_props.create_slice = self.create_slice_pre
        return {'FINISHED'}

class BAB_OP_Slice(Operator):
    """Add Slice object"""
    bl_idname = "bab.slice"
    bl_label = "Create Slice"

    @classmethod
    def poll(cls, context):
        return context.active_object

    def execute(self, context):
        obj = context.active_object
        bool_index = get_boolean_index(obj)

        if bool_index == -1:
            self.report({'ERROR'}, "Boolean not found")
            return {'FINISHED'}
        obj.modifiers[bool_index].operation = "DIFFERENCE"

        # выделяем объект
        obj.select_set(True)
        # Делаем его копию
        bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'})
        # Снимаем выделение
        obj.select_set(False)
        # Добавляем переменную
        slice_obj = context.active_object
        # Переминовываем для удобства
        slice_obj.name =  obj.name + '_Slice'
        slice_obj.modifiers[bool_index].operation = "INTERSECT"
        return {'FINISHED'}

class BAB_OP_Create_Pipe(Operator):
    """Create pipe from active object"""
    bl_idname = "bab.create_pipe"
    bl_label = "Create Pipe"
    bl_options = {'REGISTER', 'UNDO', 'PRESET'}

    @classmethod
    def poll(cls, context):
        return context.active_object and len(context.selected_objects) < 2

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        row = box.row(align=True)
        row.alignment = 'CENTER'
        row.prop(self, "wire")
        row.prop(self, "stop_calc")

        row = box.row(align=True)
        row.alignment = 'CENTER'
        row.prop(self, "save_settings")
        row.prop(self, "in_front")

        box = layout.box()
        box.label(text="Pipe:")
        box.prop(self, "offset")
        box.prop(self, "is_band")
        box.prop(self, "cap")


        box.prop(self, "refine_res")
        box.prop(self, "mean_tilt")
        box.prop(self, "twist_smooth")

        box = layout.box()
        box.label(text="Refine:")
        box.prop(self, "refine")

        box.prop(self, "refine_type")
        if self.refine_type == "DIST":
            box.prop(self, "refine_dist")

        if self.refine_type == "COUNT":
            box.prop(self, "refine_count")

        box.prop(self, "refine_multiply")
        box.prop(self, "refine_shift")
        box.prop(self, "refine_accuracy")

        box = layout.box()
        box.label(text="Self-intersect:")
        box.prop(self, "fix_corner")
        box.prop(self, "corner")


        box.prop(self, "fix_outside")

        box.prop(self, "abs_size")

        if self.abs_size:
            box.prop(self, "corner_dist")
        else:
            box.prop(self, "corner_size")

        box = layout.box()
        box.label(text="Patch Parameters:")
        box.prop(self, "smooth")
        box.prop(self, "factor")
        box.prop(self, "remove_doubles")
        box.prop(self, "subdivide")
        box.prop(self, "simplify")


    def invoke(self, context, event):
        if DEBUG:
            time_start = time.time()

        # Меняем значения
        bab_props = context.window_manager.bab_props
        self.stop_calc = bab_props.pipe_stop
        self.wire = bab_props.pipe_wire
        self.offset = bab_props.pipe_radius

        self.wire_pre = bab_props.pipe_wire
        self.offset_pre = bab_props.pipe_radius

        if DEBUG:
            print("invoke: %.4f sec\n" % (time.time() - time_start))
        return self.execute(context)



    stop_calc : BoolProperty(name="Stop calculations", default=False)
    wire: BoolProperty(name="Wire", default=False)

    offset: FloatProperty(name="Pipe Radius", default=0.05, min=0.00002, max=1000.0, step=1, subtype='DISTANCE')
    twist_smooth: IntProperty(name="Pipe Twist Smooth", default=64, min=0, max=5000)
    mean_tilt: FloatProperty(name="Mean Tilt", default=math.pi/4, min=-6.28, max=6.28, step=10, subtype='ANGLE')
    is_band: BoolProperty(name="Band", default=False)

    # refine
    refine_accuracy: IntProperty(name="Accuracy", default=64, min=1, max=1024)
    refine: BoolProperty(name="Refine", default=True)
    refine_res: IntProperty(name="Pipe Segments", default=4, min=0, max=30)
    refine_type: EnumProperty(name="Refine Type",
                                      items=(("DIST", "Dist", "Use Distantion"),
                                             ("COUNT", "Count", "Use Conunt"),
                                             ("AUTO", "Original", "Use Original Conunts"),
                                             ),
                                      description="Refine Type",
                                      default="DIST")

    refine_count: IntProperty(name="Count", default=50, min=4, max=5000)
    refine_dist: FloatProperty(name="Dist", default=0.08, min=0.001, max=1000.0, subtype='DISTANCE')
    refine_shift: FloatProperty(name="Shift", default=0.0, min=-1000, max=1000.0, subtype='DISTANCE')
    refine_multiply: FloatProperty(name="Multiply", default=1.0, min=0.001, max=1000.0)

    cap: EnumProperty(name="Cap Fill Type",
                                      items=(("NOTHING", "Nothing", "Don’t fill at all"),
                                             ("NGON", "Ngon", "Use ngons"),
                                            #  ("TRIFAN", "Triangle Fan", "Use triangle fans"),
                                             ),
                                      description="Cap Fill Type",
                                      default="NGON")

    # fixes
    fix_corner: BoolProperty(name="Fix Corners", default=True)
    corner: FloatProperty(name="Corner Profile", default=1.0, min=0.0, max=1.0)
    abs_size: BoolProperty(name="Absolute size", default=False)
    corner_size: FloatProperty(name="Corner Size", default=1.0, min=0.001, max=1.0, step=10)
    corner_dist: FloatProperty(name="Corner Radius", default=0.05, min=0.001, max=1000.0, step=1, subtype='DISTANCE')
    fix_outside: BoolProperty(name="Fix Outside Corner", default=True)

    # patch
    remove_doubles: FloatProperty(name="Remove Doubles", default=0.001, min=0.0, max=1.0, step=1, subtype='DISTANCE')
    subdivide: IntProperty(name="Subdivide Patch", default=0, min=0, max=5000)
    smooth: IntProperty(name="Smooth Patch", default=0, min=0, max=5000)
    factor: FloatProperty(name="Smooth Factor", default=0.5, min=0.0, max=1.0, step=1)
    simplify: FloatProperty(name="Simplify", default=0.0, min=0.0, max=180.0, step=1, subtype="ANGLE")

    # сохранение настроек
    save_settings: BoolProperty(name="Save settings", default=True)

    in_front: BoolProperty(name="Show in Front", default=True)

    wire_pre = False
    offset_pre = 0.05

    def execute(self, context):

        curve = context.active_object

        if self.stop_calc:
            return {'FINISHED'}
        bool_index = get_boolean_index(curve)

        is_editmode = curve.data.is_editmode
        if bool_index > -1 and not is_editmode:
            # выделяем объект
            curve.select_set(True)
            # Делаем его копию
            bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'})
            # Снимаем выделение
            curve.select_set(False)

            # Добавляем переменную
            temp_obj = context.active_object
            temp_obj.select_set(False)
            # Переминовываем для удобства
            temp_obj.name = 'Temp_OBJ'

            # Скрываем все модификаторы
            temp_obj.modifiers.foreach_set('show_viewport', [False] * len(temp_obj.modifiers))

             # Создаем группу вершин
            temp_obj.vertex_groups.new(name=temp_obj.name)
            # Добавляем в нее все вершины
            temp_obj.vertex_groups.active.add(range(len(temp_obj.data.vertices)), 1, 'ADD')

            # Применяем модификаторы до boolean
            apply_modifiers(temp_obj.modifiers, temp_obj)

            # Заходим в режим редактирования
            bpy.ops.object.mode_set(mode='EDIT')
            # Режим вершин
            bpy.ops.mesh.select_mode(type='VERT')
            # Выделяем вершины без групп
            bpy.ops.mesh.select_ungrouped(extend=False)
            # Выделяем пересечение
            bpy.ops.mesh.region_to_loop()


        if is_editmode or bool_index > -1:
            bpy.ops.mesh.duplicate_move()
            # bpy.ops.mesh.edge_face_add()
            # Удаляем дубли вершин
            if self.remove_doubles:
                bpy.ops.mesh.remove_doubles(threshold=self.remove_doubles, use_unselected=False)

            if self.simplify:
                bpy.ops.mesh.dissolve_limited(angle_limit=self.simplify, use_dissolve_boundaries=False)
            # Подразделяем, если нужно
            if self.subdivide:
                bpy.ops.mesh.subdivide(number_cuts=self.subdivide, smoothness=0.0)
            # Сглаживаем, если нужно
            if self.smooth:
                bpy.ops.mesh.vertices_smooth(factor=self.factor, repeat=self.smooth, xaxis=True, yaxis=True, zaxis=True)

            bpy.ops.mesh.separate(type='SELECTED')
            bpy.ops.object.mode_set(mode='OBJECT')
            curve.select_set(False)
            # curve.display_type = 'BOUNDS'
            curve = context.selected_objects[0]
            curve.select_set(False)

            if bool_index >-1 and not is_editmode:
                 # Удаление
                temp_obj.select_set(True)
                bpy.ops.object.delete(use_global=False)

            context.view_layer.objects.active = curve

        if curve.type == "CURVE":
            curve.select_set(True)
            bpy.ops.object.convert(target='MESH', keep_original=False)

        curve.select_set(True)
        bpy.ops.object.convert(target='CURVE', keep_original=False)
        curve.select_set(False)
        # curve.data.twist_smooth = self.twist_smooth

        for spline in curve.data.splines:
            pipe, new_curve = create_pipe(self, context, curve, self.is_band, self.cap)
            curve.data.splines.remove(spline)

            if self.fix_corner:
                indexs = find_inside(pipe, new_curve, self.offset)
                if self.is_band:
                    sides = 2
                else:
                    sides = sides=4 + 2*self.refine_res

                if fix_corner(self, pipe, indexs, sides=sides):
                    self.report({'INFO'}, "Error")

            # Удаление
            new_curve.select_set(True)
            bpy.ops.object.delete(use_global=False)

            pipe.select_set(True)
            bpy.ops.object.shade_flat()
            pipe.select_set(False)
            pipe.show_in_front = self.in_front

            if self.wire:
                pipe.show_wire = True

        curve.select_set(True)
        # pipe.select_set(True)
        bpy.ops.object.delete(use_global=False)


        bab_props = context.window_manager.bab_props
        if self.save_settings:
            bab_props.pipe_wire = self.wire
            bab_props.pipe_radius = self.offset
        else:
            bab_props.pipe_wire = self.wire_pre
            bab_props.pipe_radius = self.offset_pre

        return {'FINISHED'}

class BAB_OP_Apply_Modifiers(Operator):
    """Aplly modifiers on active object"""
    bl_idname = "bab.apply_modifiers"
    bl_label = "Apply Modifiers"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.active_object

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        box.prop(self, "skip_boolean")

    skip_boolean : BoolProperty(name="Skip boolean", default=True)


    def invoke(self, context, event):
        # Меняем значения
        bab_props = context.window_manager.bab_props
        self.skip_boolean = bab_props.apply_all
        return self.execute(context)

    def execute(self, context):
        obj = context.active_object
        # bool_index = [modifier.type for modifier in obj.modifiers].index("BOOLEAN")
        bool_index = get_boolean_index(obj)
        if self.skip_boolean and bool_index > -1:
            apply_modifiers(obj.modifiers[:bool_index], obj)
        else:
            apply_modifiers(obj.modifiers, obj)
        bab_props = context.window_manager.bab_props
        bab_props.apply_all = self.skip_boolean
        return {'FINISHED'}

class BAB_OP_Remove_Modifiers(Operator):
    """Remove modifiers on active object"""
    bl_idname = "bab.remove_modifiers"
    bl_label = "Remove Modifiers"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.active_object

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        box.prop(self, "skip_boolean")

    skip_boolean : BoolProperty(name="Skip boolean", default=True)

    def invoke(self, context, event):
        # Меняем значения
        bab_props = context.window_manager.bab_props
        self.skip_boolean = bab_props.remove_all
        return self.execute(context)

    def execute(self, context):
        obj = context.active_object

        bool_index = get_boolean_index(obj)
        if self.skip_boolean and bool_index > -1:
            remove_modifiers(obj.modifiers[:bool_index], obj)
        else:
            remove_modifiers(obj.modifiers, obj)

        bab_props = context.window_manager.bab_props
        bab_props.remove_all = self.skip_boolean
        return {'FINISHED'}

class BAB_OP_Hide_Modifiers(Operator):
    """Hide modifiers on active object"""
    bl_idname = "bab.hide_modifiers"
    bl_label = "Show/Hide Modifiers"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.active_object

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        box.prop(self, "hide")

    hide : BoolProperty(name="Show modifiers", default=True)

    def invoke(self, context, event):
        # Меняем значения
        bab_props = context.window_manager.bab_props
        self.hide = bab_props.hide
        return self.execute(context)

    def execute(self, context):
        obj = context.active_object
        obj.modifiers.foreach_set("show_viewport", [self.hide]*len(obj.modifiers))
        obj.data.update()

        bab_props = context.window_manager.bab_props
        bab_props.hide = self.hide
        return {'FINISHED'}

class BAB_OP_Visibility(Operator):
    """Change visibility on selected objects"""
    bl_idname = "bab.visibility"
    bl_label = "Apply"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):

        return context.selected_objects


    def draw(self, context):
        layout = self.layout
        box = layout.box()
        box.prop(self, "show_render")
        box.prop(self, "display_as")

    show_render: BoolProperty(name="Show in render", default=False)
    display_as: EnumProperty(name="Display as",
                                      items=(("BOUNDS", "Bounds", "On visibility"),
                                             ("WIRE", "Wire", "Off visibility"),
                                             ("SOLID", "Solid", "Off visibility"),
                                             ("TEXTURED", "Textured", "Off visibility"),
                                             ),
                                      description="Display as",
                                      default="TEXTURED")

    def invoke(self, context, event):
        # Меняем значения
        bab_props = context.window_manager.bab_props
        self.show_render = bab_props.show_render
        self.display_as = bab_props.display_as
        return self.execute(context)

    def execute(self, context):
        for obj in context.selected_objects:
            obj.display_type = self.display_as
            obj.hide_render = not self.show_render

        bab_props = context.window_manager.bab_props
        bab_props.show_render = self.show_render
        bab_props.display_as = self.display_as
        return {'FINISHED'}

class BAB_OP_Symmetrize(Operator):
    """Symmetrize active object"""
    bl_idname = "bab.symmetrize"
    bl_label = "Symmetrize"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.active_object

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        row = box.row(align=True)
        row.alignment = 'CENTER'
        row.prop(self, "axis")

        box.prop(self, "flip_direction")
        box.prop(self, "merge_threshold")

    axis: EnumProperty(name="Axis",
                                items=(("X", "X", "Use x axis"),
                                        ("Y", "Y", "Use y axis"),
                                        ("Z", "Z", "Use z axis"),
                                        ),
                                description="Axis",
                                default="X")


    flip_direction: BoolProperty(name="Flip Direction", default=False)

    merge_threshold: FloatProperty(name="Merge Dist", default=0.001, min=0.0, max=1.0, step=1, subtype='DISTANCE')

    def invoke(self, context, event):
            # Меняем значения
            bab_props = context.window_manager.bab_props
            self.axis = bab_props.axis
            self.flip_direction = bab_props.flip_direction
            self.merge_threshold = bab_props.merge_threshold
            return self.execute(context)

    def execute(self, context):
        obj = context.active_object
        # Удаляем группы вершин
        if obj.vertex_groups:
            obj.vertex_groups.clear()


        # выделяем объект
        obj.select_set(True)
        # Делаем его копию
        bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'})
        # Снимаем выделение
        obj.select_set(False)
        # Добавляем переменную
        transfer_obj = context.active_object
        # Переминовываем для удобства
        transfer_obj.name = 'BAB_TRANSFER'

        # Меняем отображение для удобства
        transfer_obj.display_type = 'BOUNDS'
        # Меняем shading объектов
        bpy.ops.object.shade_smooth()
        # Снимаем выделение
        transfer_obj.select_set(False)
        # делаем активным исходный объект
        context.view_layer.objects.active = obj
        obj.select_set(True)

        modifier = obj.modifiers.new(name="BAB_Mirror", type="MIRROR")
        modifier.show_viewport = False
        modifier.use_mirror_merge = True
        modifier.use_mirror_vertex_groups = False
        modifier.merge_threshold = self.merge_threshold

        modifier.use_bisect_axis = [True, True, True]
        modifier.use_bisect_flip_axis = [self.flip_direction]*3
        if self.axis == 'X':
            modifier.use_axis = [True, False, False]
        elif self.axis == 'Y':
            modifier.use_axis = [False, True, False]
        else:
            modifier.use_axis = [False, False, True]

        bpy.ops.object.modifier_apply(modifier=modifier.name)

        if self.axis == 'X':
            indexs = [v.index for v in obj.data.vertices if v.co.x == 0.0]
        elif self.axis == 'Y':
            indexs = [v.index for v in obj.data.vertices if v.co.y == 0.0]
        else:
            indexs = [v.index for v in obj.data.vertices if v.co.z == 0.0]

        # Создаем группу вершин
        obj.vertex_groups.new(name="transfer")

        if indexs:
            # Добавляем в нее indexs
            obj.vertex_groups.active.add(indexs, 1, 'ADD')

            data_transfer_modifier = obj.modifiers.new(name="BAB_Transfer", type="DATA_TRANSFER")
            data_transfer_modifier.show_viewport = False
            data_transfer_modifier.object = transfer_obj
            data_transfer_modifier.use_loop_data = True
            data_transfer_modifier.data_types_loops = {"CUSTOM_NORMAL"}
            data_transfer_modifier.loop_mapping = "POLYINTERP_NEAREST"
            data_transfer_modifier.vertex_group = 'transfer'
            bpy.ops.object.modifier_apply(modifier=data_transfer_modifier.name)

        obj.select_set(False)
        transfer_obj.select_set(True)

        bpy.ops.object.delete(use_global=False)
        obj.select_set(True)


        bab_props = context.window_manager.bab_props
        bab_props.axis = self.axis
        bab_props.flip_direction = self.flip_direction
        bab_props.merge_threshold = self.merge_threshold
        return {'FINISHED'}

# functions

def create_pipe(self, context, src_curve, is_band=False, cap="NGON"):
    if DEBUG:
        time_start = time.time()

    # создаем новую кривую
    curve_data = bpy.data.curves.new('BAB_PIPE', type='CURVE')


    # src_curve.data.twist_smooth = self.twist_smooth
    # создаем новый сплайн
    spline = curve_data.splines.new("BEZIER")
    spline.use_cyclic_u = src_curve.data.splines[0].use_cyclic_u

    if self.refine:
        # узнаем длинну сплайна
        spline_length = src_curve.data.splines[0].calc_length()

        if self.refine_type == "DIST":
            refine_count = round((spline_length/self.refine_dist)*self.refine_multiply)
        elif self.refine_type == "AUTO":
            refine_count = round(len(src_curve.data.splines[0].points)*self.refine_multiply)
        else:
            refine_count = round(self.refine_count*self.refine_multiply)

        if refine_count < 4:
            refine_count = 4
        src_curve.data.resolution_u = self.refine_accuracy
        # добавляем в него нужное количеств вершин
        # spline.points.add(refine_count-1)
        spline.bezier_points.add(refine_count-1)

        # Создаем новый массив координат для вершин
        points_co = np.zeros(refine_count*3)
        # заполяем x с равномерным смещением
        if spline.use_cyclic_u:
            points_co[::3] = np.linspace(0+self.refine_shift, spline_length+self.refine_shift, refine_count, endpoint=False)
        else:
            if self.refine_shift == 0.0:
                refine_shift = 0.001
                # refine_shift = 0.0
            else:
                refine_shift = self.refine_shift
            points_co[::3] = np.linspace(0-refine_shift, spline_length+refine_shift, refine_count, endpoint=True)
        # заменяем x координаты
        # spline.points.foreach_set('co', points_co)
        spline.bezier_points.foreach_set('co', points_co)
        spline.bezier_points.foreach_set('handle_left', points_co)
        spline.bezier_points.foreach_set('handle_right', points_co)

        spline.bezier_points[0].handle_left = spline.bezier_points[1].co
        spline.bezier_points[0].handle_right = spline.bezier_points[1].co

        spline.bezier_points[-1].handle_left = spline.bezier_points[-2].co
        spline.bezier_points[-1].handle_right = spline.bezier_points[-2].co



    else:
        # добавляем в него нужное количеств вершин
        # spline.points.add(len(src_curve.data.splines[0].points)-1)
        spline.bezier_points.add(len(src_curve.data.splines[0].points)-1)
        # Создаем новый массив координат для вершин
        points_co = np.empty(len(src_curve.data.splines[0].points)*4)
        src_curve.data.splines[0].points.foreach_get('co', points_co)

        points_co1 = np.empty(len(src_curve.data.splines[0].points)*3)

        points_co1[0::3] = points_co[0::4]
        points_co1[1::3] = points_co[1::4]
        points_co1[2::3] = points_co[2::4]


        # заменяем x координаты
        # spline.points.foreach_set('co', points_co)
        spline.bezier_points.foreach_set('co', points_co1)

        spline.bezier_points.foreach_set('co', points_co1)
        spline.bezier_points.foreach_set('handle_left', points_co1)
        spline.bezier_points.foreach_set('handle_right', points_co1)

        spline.bezier_points[0].handle_left = spline.bezier_points[1].co
        spline.bezier_points[0].handle_right = spline.bezier_points[1].co

        spline.bezier_points[-1].handle_left = spline.bezier_points[-2].co
        spline.bezier_points[-1].handle_right = spline.bezier_points[-2].co

    # spline.points.foreach_set('tilt', [self.mean_tilt] * len(spline.points))
    spline.bezier_points.foreach_set('tilt', [self.mean_tilt] * len(spline.bezier_points))


    # spline.bezier_points.foreach_set('handle_left_type', [2] * len(spline.bezier_points))
    # spline.bezier_points.foreach_set('handle_right_type', [2] * len(spline.bezier_points))



    curve = bpy.data.objects.new('BAB_CURVE', curve_data)
    curve.matrix_world = src_curve.matrix_world
    context.collection.objects.link(curve)

    curve.select_set(True)
    context.view_layer.objects.active = curve

    if self.refine:
        curve_modifier = curve.modifiers.new(name="Refine", type='CURVE')
        curve_modifier.show_viewport = False
        curve_modifier.use_apply_on_spline = True
        curve_modifier.object = src_curve
        bpy.ops.object.modifier_apply(modifier=curve_modifier.name)

    # curve.show_wire = True
    curve_data.dimensions = '3D'
    curve_data.resolution_u = 1
    curve_data.fill_mode = 'FULL'
    if is_band:
        curve_data.extrude = self.offset
    else:
        curve_data.bevel_depth = self.offset
    curve_data.bevel_resolution = self.refine_res
    curve_data.twist_smooth = self.twist_smooth
    # curve_data.twist_mode = "TANGENT"

    # конвертируем в mesh
    bpy.ops.object.convert(target='MESH', keep_original=True)
    curve.select_set(False)

    pipe = context.selected_objects[0]
    pipe.name = "BAB_PIPE"
    if not spline.use_cyclic_u and not is_band and cap != "NOTHING":
        context.view_layer.objects.active = pipe
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_mode(type='EDGE')
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.region_to_loop()
        bpy.ops.mesh.edge_face_add()
        if cap == "TRIFAN":
            bpy.ops.mesh.poke()
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')
    pipe.select_set(False)

     # Создаем группы вершин
    pipe.vertex_groups.new(name="temp")
    pipe.vertex_groups.new(name="temp")
    pipe.vertex_groups.new(name="bevel")

    # Добавляем в нее все вершины
    pipe.vertex_groups.active.add(range(len(pipe.data.vertices)), 1, 'ADD')

    if DEBUG:
        print("create_pipe: %.4f sec\n" % (time.time() - time_start))
    return pipe, curve

def get_boolean_index(obj):
    """Find and return First Boolean modifer index"""
    if DEBUG:
        time_start = time.time()
    index = -1
    for i, modifier in enumerate(obj.modifiers):
        if modifier.type == "BOOLEAN":
            index = i
            break
    if DEBUG:
        print("get_boolean_index: %.4f sec\n" % (time.time() - time_start))
    return index

def get_subsurf_index(obj):
    """Find and return lastSubSurf modifer index"""
    if DEBUG:
        time_start = time.time()
    index = -1
    for i, modifier in enumerate(obj.modifiers):
        if modifier.type == "SUBSURF":
            index = i
    if DEBUG:
        print("get_subsurf_index: %.4f sec\n" % (time.time() - time_start))
    return index

def apply_modifiers(modifiers, obj):
    if DEBUG:
        time_start = time.time()
    for modifier in modifiers:
        try:
            bpy.ops.object.modifier_apply(modifier=modifier.name)
        except:
            obj.modifiers.remove(modifier)
    if DEBUG:
        print("apply_modifiers: %.4f sec\n" % (time.time() - time_start))

def remove_modifiers(modifiers, obj):
    if DEBUG:
        time_start = time.time()
    for modifier in modifiers:
        obj.modifiers.remove(modifier)
    if DEBUG:
        print("apply_modifiers: %.4f sec\n" % (time.time() - time_start))

def find_inside(obj, guide, radius):
    """Find verteces inside pipe"""
    if DEBUG:
        time_start = time.time()

    indexs = set()
    kd = create_tree(obj)
    print("Создание дерева: %.4f sec\n" % (time.time() - time_start))
    for point in guide.data.splines[0].bezier_points:
        indexs = indexs | {index for (co, index, dist) in kd.find_range(point.co, radius-0.00001)}
    if DEBUG:
        print("find_inside: %.4f sec\n" % (time.time() - time_start))
    return indexs

def create_tree(obj):
    size = len(obj.data.vertices)
    kd = mathutils.kdtree.KDTree(size)
    for i, v in enumerate(obj.data.vertices):
        kd.insert(v.co, i)
    kd.balance()
    return kd

def fix_corner(self, obj, indexs, sides=12):
    """Try to fix corners"""
    if DEBUG:
        time_start = time.time()

    if not indexs:
        False

    if self.fix_outside:
        out_indexs = set()
        for i in indexs:
            loop_int = i//sides*sides
            out_indexs = out_indexs | {j for j in range(loop_int, loop_int+sides)}
        indexs = out_indexs

    while indexs:
        temp_index = indexs.pop()
        loop_int = temp_index - temp_index//sides*sides
        obj_indexs = [i for i in range(len(obj.data.vertices))]
        loop = obj_indexs[loop_int::sides]


        temp_index = loop.index(temp_index) + len(loop)

        cyclyc_loop = loop*3

        left_index = temp_index - 1
        rigt_index = temp_index + 1

        while cyclyc_loop[left_index] in indexs:
            indexs.discard(cyclyc_loop[left_index])
            left_index -= 1

        while cyclyc_loop[rigt_index] in indexs:
            indexs.discard(cyclyc_loop[rigt_index])
            rigt_index += 1

        if len(cyclyc_loop[left_index:rigt_index]) >= len(loop):
            return True

        line_a1 = obj.data.vertices[cyclyc_loop[rigt_index]].co
        line_a2 = obj.data.vertices[cyclyc_loop[rigt_index+1]].co

        line_b1 = obj.data.vertices[cyclyc_loop[left_index]].co
        line_b2 = obj.data.vertices[cyclyc_loop[left_index-1]].co

        intesect_points = mathutils.geometry.intersect_line_line(line_a1, line_a2, line_b1, line_b2)

        if intesect_points:
            new_coord = (intesect_points[0] + intesect_points[0]) / 2
        else:
            return True

        dist1 = line_a1 - new_coord
        dist2 = line_b1 - new_coord


        if self.abs_size:
            corner_multiply1 = self.corner_dist
            corner_multiply2 = self.corner_dist

            if corner_multiply1 > dist2.length:
                corner_multiply1 = dist2.length
            if corner_multiply2 > dist1.length:
                corner_multiply2 = dist1.length


        else:
            corner_multiply1 = dist2.length*self.corner_size
            corner_multiply2 = dist1.length*self.corner_size

        knot1 = new_coord + dist2.normalized()*corner_multiply1
        knot2 = new_coord + dist1.normalized()*corner_multiply2

        # This is the correct method, but it doesn’t work as well as the wrong one.
        # handle1 = new_coord
        # handle2 = new_coord

        handle1 = (knot1 - line_b2).normalized()*self.corner*corner_multiply1 + knot1
        handle2 = (knot2 - line_a2).normalized()*self.corner*corner_multiply2 + knot2



        resolution = len(cyclyc_loop[left_index:rigt_index+1])
        new_points = mathutils.geometry.interpolate_bezier(knot1, handle1, handle2, knot2, resolution)
        test = cyclyc_loop[left_index: rigt_index+1]

        for i in range(1, resolution-1):
            obj.data.vertices[test[i]].co = new_points[i]

    if DEBUG:
        print("fix_inside: %.4f sec\n" % (time.time() - time_start))

    return False

def do_boolean(context, curve_cut, obj):
    if DEBUG:
        time_start = time.time()
    curve_cut.display_type = 'BOUNDS'
    boolean_modifier = obj.modifiers.new(name="BooleanBevel", type='BOOLEAN')
    boolean_modifier.show_viewport = False
    boolean_modifier.object = curve_cut
    boolean_modifier.operation = "UNION"
    # boolean_modifier.double_threshold = 0.0
    # bpy.ops.object.select_all(action='DESELECT')
    context.view_layer.objects.active = obj
    bpy.ops.object.modifier_apply(modifier=boolean_modifier.name)
    if DEBUG:
        print("do_boolean: %.4f sec\n" % (time.time() - time_start))

def prepare_in_obj(self, context, src_obj):
    if DEBUG:
        time_start = time.time()
    # Снимаем выделение со всех объектов
    bpy.ops.object.select_all(action='DESELECT')
    # объект boolean
    bool_obj = src_obj.modifiers[self.bool_index].object
    # Режим отрисовки для boolean
    bool_obj.display_type = 'BOUNDS'

    if DEBUG:
        print("prepare_in_obj: %.4f sec\n" % (time.time() - time_start))

    # Удаляем группы вершин
    if src_obj.vertex_groups:
        src_obj.vertex_groups.clear()

    # Удаляем группы вершин
    if bool_obj.vertex_groups:
        bool_obj.vertex_groups.clear()


    # Скрываем все модификаторы
    src_obj.modifiers.foreach_set('show_viewport', [False] * len(src_obj.modifiers))

    # Создаем группу вершин
    src_obj.vertex_groups.new(name=src_obj.name)
    # Добавляем в нее все вершины
    src_obj.vertex_groups.active.add(range(len(src_obj.data.vertices)), 1, 'ADD')

    # Меняем подразделение
    change_subsurf(src_obj, self.src_subsrf_index, self.subdiv_a)
    change_subsurf(bool_obj, self.bool_subsrf_index, self.subdiv_b)
    # Применяем модификаторы до boolean
    apply_modifiers(src_obj.modifiers[:self.bool_index], src_obj)

    if self.create_slice:

        # выделяем объект
        src_obj.select_set(True)
        # Делаем его копию
        bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'})
        # Снимаем выделение
        src_obj.select_set(False)
        # Добавляем переменную
        slice_obj = context.active_object
        # Переминовываем для удобства
        slice_obj.name = src_obj.name + 'SLICE'
        slice_obj.select_set(False)
        # делаем активным исходный объект
        context.view_layer.objects.active = src_obj
        # Меняем оперцию
        self.operation = "DIFFERENCE"
        slice_obj.modifiers[0].operation = "INTERSECT"

    else:
        slice_obj = False
    # Меняем оперцию
    src_obj.modifiers[0].operation = self.operation

    if self.transfer:
        # выделяем объект
        src_obj.select_set(True)
        # Делаем его копию
        bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'})
        # Снимаем выделение
        src_obj.select_set(False)
        # Добавляем переменную
        transfer_obj = context.active_object
        # Переминовываем для удобства
        transfer_obj.name = 'BAB_TRANSFER'
        # Меняем отображение для удобства
        transfer_obj.display_type = 'BOUNDS'

        bool_obj.select_set(True)
        # выделяем исходный объект
        src_obj.select_set(True)
        if self.create_slice:
            slice_obj.select_set(True)
        # Меняем shading объектов
        bpy.ops.object.shade_smooth()

        # Снимаем выделение
        transfer_obj.select_set(False)
        bool_obj.select_set(False)
        if self.create_slice:
            slice_obj.select_set(False)

        # делаем активным исходный объект
        context.view_layer.objects.active = src_obj
        # включаем автосглаживание
        context.object.data.use_auto_smooth = True
        # угол авто скглаживания
        context.object.data.auto_smooth_angle = 3.14159
        # снимаем выделение
        src_obj.select_set(False)
    else:
        transfer_obj = False

    # Применяем булеан
    bpy.ops.object.modifier_apply(modifier=src_obj.modifiers[0].name)
    # Применяем масштаб
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    # Заходим в режим редактирования
    bpy.ops.object.mode_set(mode='EDIT')
    # Режим вершин
    bpy.ops.mesh.select_mode(type='VERT')
    # Выделяем вершины без групп
    bpy.ops.mesh.select_ungrouped(extend=False)
    # группа вершин для части булена
    src_obj.vertex_groups.new(name=bool_obj.name)
    bpy.ops.object.vertex_group_assign()
    # Выделяем пересечение
    bpy.ops.mesh.region_to_loop()
    bpy.ops.object.vertex_group_remove_from()

    bpy.ops.mesh.duplicate_move()
    # Удаляем дубли вершин
    if self.remove_doubles:
        bpy.ops.mesh.remove_doubles(threshold=self.remove_doubles, use_unselected=False)

    if self.simplify:
         bpy.ops.mesh.dissolve_limited(angle_limit=self.simplify, use_dissolve_boundaries=False)
    # Подразделяем, если нужно
    if self.subdivide:
        bpy.ops.mesh.subdivide(number_cuts=self.subdivide, smoothness=0.0)
    # Сглаживаем, если нужно
    if self.smooth:
        bpy.ops.mesh.vertices_smooth(factor=self.factor, repeat=self.smooth, xaxis=True, yaxis=True, zaxis=True)

    # группа вершин для пересечения
    src_obj.vertex_groups.new(name="bevel")
    bpy.ops.mesh.separate(type='SELECTED')
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.hide(unselected=False)
    bpy.ops.object.mode_set(mode='OBJECT')

    curve = context.selected_objects[0]
    context.view_layer.objects.active = curve
    curve.name = "BAB_GUIDE"
    bpy.ops.object.convert(target='CURVE', keep_original=False)
    curve.select_set(False)

    if self.create_slice:
        # делаем активным исходный объект
        context.view_layer.objects.active = slice_obj
        # Применяем булеан
        bpy.ops.object.modifier_apply(modifier=slice_obj.modifiers[0].name)
        # Применяем масштаб
        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
        # Заходим в режим редактирования
        bpy.ops.object.mode_set(mode='EDIT')
        # Режим вершин
        bpy.ops.mesh.select_mode(type='VERT')
        # Выделяем вершины без групп
        bpy.ops.mesh.select_ungrouped(extend=False)
        # группа вершин для части булена
        slice_obj.vertex_groups.new(name=bool_obj.name)
        bpy.ops.object.vertex_group_assign()
        # Выделяем пересечение
        bpy.ops.mesh.region_to_loop()
        bpy.ops.object.vertex_group_remove_from()
        # группа вершин для пересечения
        slice_obj.vertex_groups.new(name="bevel")
        # bpy.ops.mesh.separate(type='SELECTED')
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.hide(unselected=False)
        context.view_layer.objects.active = curve
        bpy.ops.object.mode_set(mode='OBJECT')

    return curve, bool_obj, transfer_obj, slice_obj

def prepare_in_edit(self, context, src_obj):
    self.operation = "UNION"
    self.create_slice = False
    # Удаляем группы вершин
    if src_obj.vertex_groups:
        src_obj.vertex_groups.clear()

    # Создаем группу вершин
    src_obj.vertex_groups.new(name=src_obj.name)

    # Создаем группу вершин
    src_obj.vertex_groups.new(name=src_obj.name)


    bpy.ops.mesh.duplicate_move()
    # Удаляем дубли вершин
    if self.remove_doubles:
        bpy.ops.mesh.remove_doubles(threshold=self.remove_doubles, use_unselected=False)

    if self.simplify:
         bpy.ops.mesh.dissolve_limited(angle_limit=self.simplify, use_dissolve_boundaries=False)
    # Подразделяем, если нужно
    if self.subdivide:
        bpy.ops.mesh.subdivide(number_cuts=self.subdivide, smoothness=0.0)
    # Сглаживаем, если нужно
    if self.smooth:
        bpy.ops.mesh.vertices_smooth(factor=self.factor, repeat=self.smooth, xaxis=True, yaxis=True, zaxis=True)

    # группа вершин для пересечения
    src_obj.vertex_groups.new(name="bevel")
    bpy.ops.mesh.separate(type='SELECTED')
    bpy.ops.mesh.hide(unselected=True)
    bpy.ops.object.mode_set(mode='OBJECT')

    src_obj.select_set(False)
    curve = context.selected_objects[0]
    context.view_layer.objects.active = curve
    curve.name = "BAB_GUIDE"
    bpy.ops.object.convert(target='CURVE', keep_original=False)
    curve.select_set(False)

    if self.transfer:
        # выделяем объект
        src_obj.select_set(True)
        context.view_layer.objects.active = src_obj

        # Делаем его копию
        bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'})
        # Снимаем выделение
        src_obj.select_set(False)
        # Добавляем переменную
        transfer_obj = context.active_object
        # Переминовываем для удобства
        transfer_obj.name = 'BAB_TRANSFER'
        # Меняем отображение для удобства
        transfer_obj.display_type = 'BOUNDS'

        # выделяем исходный объект
        src_obj.select_set(True)
        # Меняем shading объектов
        bpy.ops.object.shade_smooth()

        # Снимаем выделение
        transfer_obj.select_set(False)

        # делаем активным исходный объект
        context.view_layer.objects.active = src_obj
        # включаем автосглаживание
        context.object.data.use_auto_smooth = True
        # угол авто скглаживания
        context.object.data.auto_smooth_angle = 3.14159
        # снимаем выделение
        src_obj.select_set(False)
        bool_obj = transfer_obj
    else:
        transfer_obj = False
        bool_obj = False
    # Добавляем в нее все вершины
    src_obj.vertex_groups[0].add(range(len(src_obj.data.vertices)), 1, 'ADD')
    # Добавляем в нее все вершины
    src_obj.vertex_groups[1].add(range(len(src_obj.data.vertices)), 1, 'ADD')
    return curve, bool_obj, transfer_obj

def change_subsurf(obj, index, level):
    """Change subsurf level on obj"""
    if DEBUG:
        time_start = time.time()
    if index > -1:
        obj.modifiers[index].levels = level
    if DEBUG:
        print("change_subsurf: %.4f sec\n" % (time.time() - time_start))

def align(obj, pipe, guide, sides=12):
    if DEBUG:
        time_start = time.time()

    cyclic = guide.data.splines[0].use_cyclic_u
    # Создаем дерево
    kd = create_tree(pipe)

    for v in obj.data.vertices:
        if v.groups and not v.hide:
            # co, index, dist = kd.find(v.co)
            index = kd.find(v.co)[1]
            v.co = guide.data.splines[0].bezier_points[index//sides - cyclic].co
            obj.vertex_groups[2].remove([v.index])
    if DEBUG:
        print("align: %.4f sec\n" % (time.time() - time_start))

def do_bevel(self):
    if DEBUG:
        time_start = time.time()
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_mode(type='VERT')
    bpy.ops.mesh.select_all(action='DESELECT')

    bpy.ops.object.vertex_group_select()

    if self.bevel_segments > 1:
        bevel_segments = self.bevel_segments - 1
        bevel_width = 100 - (200 / (bevel_segments + 3))
        bpy.ops.mesh.bevel(vertex_only=False, offset_type='PERCENT', profile=self.bevel_profile,
                        segments=bevel_segments,
                        clamp_overlap=False, offset_pct=bevel_width, mark_sharp=False,mark_seam=False,loop_slide=True)

    if self.bevel_segments == 0:
        bpy.ops.mesh.dissolve_edges(use_verts=True, use_face_split=False)
    bpy.ops.object.vertex_group_remove_from()

    bpy.ops.mesh.select_all(action='DESELECT')
    # bpy.ops.mesh.hide(unselected=True)

    if DEBUG:
        print("DO_BEVEL: %.4f sec\n" % (time.time() - time_start))

classes = (

    BAB_Props,
    BAB_PT_Panel,

    BAB_OP_Bevel,

    BAB_OP_Slice,
    BAB_OP_Create_Pipe,

    BAB_OP_Apply_Modifiers,
    BAB_OP_Remove_Modifiers,
    BAB_OP_Hide_Modifiers,

    BAB_OP_Visibility,

    BAB_OP_Symmetrize,

)



def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.WindowManager.bab_props = bpy.props.PointerProperty(type=BAB_Props)


def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    del bpy.types.WindowManager.bab_props

if __name__ == "__main__":
    register()
