bl_info = { 
    "name": "Smart Click",
    "location": "3D View > Tools > Smart Click",
    "category": "3D View",
    "blender": (2, 90, 0),
    "version": (0, 3, 3),
    "author": "Gregory Smirnov"
}




import bpy
from bpy.types import WorkSpaceTool, AddonPreferences
from bpy.props import FloatProperty, BoolProperty, IntVectorProperty, IntProperty, EnumProperty



def Poly_Count_Select(mode):
    
    '''Function select objects by poly count'''
    
    obj = bpy.context.active_object
    
    object_list = []
    
    base_count = len(obj.data.polygons)

    for item in bpy.data.objects:
        if item.type=='MESH':
            item_count = len(item.data.polygons)
            if mode=='>':
                if item_count > base_count:
                    object_list.append(item)
            if mode=='<':
                if item_count < base_count:
                    object_list.append(item)
            if mode=='==':
                if item_count == base_count:
                    object_list.append(item)
                    
    for item in object_list:
        item.select_set(True)                
                    
    return  object_list


def Bounding_Box_Volume(obj):


        bb = [v[:] for v in obj.bound_box]

        z_axis = [bb[0], bb[1]]
        y_axis = [bb[0], bb[3]]
        x_axis = [bb[0], bb[4]]

        z = bb[1][2]-bb[0][2]
        y = bb[3][1]-bb[0][1]
        x = bb[4][0]-bb[0][0]
        
        sz = obj.scale[2]
        sy = obj.scale[1]
        sx = obj.scale[0]
                            
        z_scale = z*sz
        y_scale = y*sy
        x_scale = x*sx
        
        if obj.parent:
            z_scale = z_scale*obj.parent.scale[2]
            y_scale = y_scale*obj.parent.scale[1]
            x_scale = x_scale*obj.parent.scale[0]
      
        volume = x_scale*y_scale*z_scale
        
        if volume < 0:
            volume = volume*(-1)
        
        return volume
    
    
    
def Bounds_Volume_Select(mode):
    
    '''Function select objects by poly count'''
    
    obj = bpy.context.active_object
    
    
    object_list = []
    
    base_volume = Bounding_Box_Volume(obj)

    for item in bpy.data.objects:
        if item.type=='MESH':
            
            item_volume = Bounding_Box_Volume(item)
            
            if mode=='>':
                if item_volume > base_volume:
                    object_list.append(item)
            if mode=='<':
                if item_volume < base_volume:
                    object_list.append(item)
            if mode=='==':
                if item_volume == base_volume:
                    object_list.append(item)
                    
    for item in object_list:
        item.select_set(True)                
                    
    return  object_list    


def return_items():

    type_items = (
    ("CHILDREN_RECURSIVE", "Children", ""),
    ("CHILDREN", "Immediate Children", ""),
    ("PARENT", "Parent", ""),
    ("SIBLINGS", "Siblinks", ""),
    ("TYPE", "Type", ""),
    ("COLLECTION", "Collection", ""),
    ("HOOK", "Hook", ""),
    ("PASS", "Pass", ""),
    ("COLOR", "Color", ""),
    ("KEYINGSET", "Keying Set", ""),
    ("LIGHT_TYPE", "Light Type", ""),
    ("OBDATA", "Object Data", ""),
    ("MATERIAL", "Material", ""),
    ("DUPGROUP", "Instanced Collection", ""),
    ("PARTICLE", "Particle System", ""),
    ("LIBRARY_OBDATA", "Library (Object Data)", ""),
    ("POLY_COUNT", "Poly Count", "Select objects by polycount clicked object"),
    ("BB_VOLUME", "Bounding Box Volume", "Select objects by bounds volume clicked object"),
    )
    
    return type_items

def bool_items():
    
    type_items = (
    (">", "Select then > polygons", ""),
    ("<", "Select then < polygons", ""),
    ("==", "Select then = polygons", ""),
    )
    
    return type_items 

def bool_items_bbv():
    
    type_items = (
    (">", "Select then volume >", ""),
    ("<", "Select then volume <", ""),
    ("==", "Select then volume =", ""),
    )
    
    return type_items   


class SmartSelectTool(WorkSpaceTool):  
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'OBJECT'
    bl_idname = "tool.smart_select"
    bl_label = "Smart Click"
    bl_description = ("Click-Select by grouped or lincked type")
    #bl_icon =  "ops.gpencil.draw.poly"          
    #bl_icon =  "ops.generic.select"          
    bl_icon =  "brush.uv_sculpt.relax"          
    bl_widget = None 
    bl_keymap = (
        ("wm.smart_select_tool", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": [("select_type", 1 )]}),
        ("wm.smart_select_tool_add", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
         {"properties": [("select_type", 11 )]}),
        ("wm.smart_select_tool_sub", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
         {"properties": [("select_type", 11)]}),       
         
    )
    
    def draw_settings(context, layout, tool):
        layout.prop(context.scene, 'select_type') 
        if context.scene.select_type != 'POLY_COUNT' and context.scene.select_type != 'BB_VOLUME':       
            layout.label(text='Click - Select. Shift-Click - Add. Ctrl-Click - Substract')
            
        if context.scene.select_type == 'POLY_COUNT':       
            layout.prop(context.scene, 'select_bool') 
            
        if context.scene.select_type == 'BB_VOLUME':       
            layout.prop(context.scene, 'select_bbv')                       
        
            
    
class OP_SMART_SELECT_TOOL(bpy.types.Operator):
    bl_idname = "wm.smart_select_tool"
    bl_label = "Select Collection Tool"

     
    extend : BoolProperty(default = False)
    deselect : BoolProperty(default = False)
    toggle : BoolProperty(default = False)
    center : BoolProperty(default = False)
    enumerate : BoolProperty(default = False)
    object : BoolProperty(default = False)
    location : IntVectorProperty(default = (0,0),subtype ='XYZ', size = 2)

    def execute(self, context):
        
        
        bpy.ops.view3d.select(extend=self.extend, deselect=self.deselect, toggle=self.toggle, center=self.center, enumerate=self.enumerate, object=self.object, location=(self.location[0] , self.location[1] ))
        try:
            try:
                bpy.ops.object.select_grouped(type=context.scene.select_type)
            except:
                bpy.ops.object.select_linked(type=context.scene.select_type)
                     
                
        except:
            try:
               if context.scene.select_type == 'POLY_COUNT':
                    Poly_Count_Select(context.scene.select_bool) 
                    
               if context.scene.select_type == 'BB_VOLUME':
                    Bounds_Volume_Select(context.scene.select_bbv)                     
            except:    
                self.report({'INFO'}, 'The selection type is not suitable for the object')
           
        return {'FINISHED'}

    def invoke(self, context, event):
        if context.space_data.type == 'VIEW_3D':
            self.location[0] = event.mouse_region_x
            self.location[1]  = event.mouse_region_y
            return self.execute(context)
        else:
                self.report({'WARNING'}, "Active space must be a View3d")
                return {'CANCELLED'} 
            
            

class OP_SMART_SELECT_TOOL_ADD(bpy.types.Operator):
    bl_idname = "wm.smart_select_tool_add"
    bl_label = "Selection Tool"
    
    
    
    extend : BoolProperty(default = False)
    deselect : BoolProperty(default = False)
    toggle : BoolProperty(default = False)
    center : BoolProperty(default = False)
    enumerate : BoolProperty(default = False)
    object : BoolProperty(default = False)
    location : IntVectorProperty(default = (0,0),subtype ='XYZ', size = 2)

    def execute(self, context):
        
        old_selection = bpy.context.selected_objects
        
        bpy.ops.view3d.select(extend=self.extend, deselect=self.deselect, toggle=self.toggle, center=self.center, enumerate=self.enumerate, object=self.object, location=(self.location[0] , self.location[1] ))
        try:
            try:
                bpy.ops.object.select_grouped(type=context.scene.select_type)
            except:
                bpy.ops.object.select_linked(type=context.scene.select_type)
                
            print('\n')
            
            for item in old_selection:
                print('Old selection: ', item.name)  
                
                
            new_selection = bpy.context.selected_objects                          
            
            for item in new_selection:
                old_selection.append(item)
                
            print('\n')
            
                
            for item in old_selection:
                item.select_set(True)
                print('New selection: ', item.name)       
                
        except:
            self.report({'INFO'}, 'The selection type is not suitable for the object')
           
        return {'FINISHED'}

    def invoke(self, context, event):
        if context.space_data.type == 'VIEW_3D':
            self.location[0] = event.mouse_region_x
            self.location[1]  = event.mouse_region_y
            return self.execute(context)
        else:
                self.report({'WARNING'}, "Active space must be a View3d")
                return {'CANCELLED'} 
            
                        
class OP_SMART_SELECT_TOOL_SUB(bpy.types.Operator):
    bl_idname = "wm.smart_select_tool_sub"
    bl_label = "Selection Tool"

     
    extend : BoolProperty(default = False)
    deselect : BoolProperty(default = False)
    toggle : BoolProperty(default = False)
    center : BoolProperty(default = False)
    enumerate : BoolProperty(default = False)
    object : BoolProperty(default = False)
    location : IntVectorProperty(default = (0,0),subtype ='XYZ', size = 2)

    def execute(self, context):
        
        old_selection = bpy.context.selected_objects
        
        bpy.ops.view3d.select(extend=self.extend, deselect=self.deselect, toggle=self.toggle, center=self.center, enumerate=self.enumerate, object=self.object, location=(self.location[0] , self.location[1] ))
        try:
            try:
                bpy.ops.object.select_grouped(type=context.scene.select_type)
            except:
                bpy.ops.object.select_linked(type=context.scene.select_type)
                
            print('\n')
            
            for item in old_selection:
                print('Old selection: ', item.name)  
                
                
            new_selection = bpy.context.selected_objects                          
             
                     
            for item in new_selection:
                    print('Remove item: ',item.name)
                    old_selection.remove(item) 
                
            print('\n')
            
            bpy.ops.object.select_all(action='DESELECT')
                
            for item in old_selection:
                item.select_set(True)
                print('New selection: ', item.name)       
                
        except:
            self.report({'INFO'}, 'The selection type is not suitable for the object')
           
        return {'FINISHED'}

    def invoke(self, context, event):
        if context.space_data.type == 'VIEW_3D':
            self.location[0] = event.mouse_region_x
            self.location[1]  = event.mouse_region_y
            return self.execute(context)
        else:
                self.report({'WARNING'}, "Active space must be a View3d")
                return {'CANCELLED'} 
            

            
class OT_SMART_CLICK_POLY_TOOL(WorkSpaceTool):  
    bl_space_type = 'VIEW_3D'
    bl_context_mode = 'EDIT_MESH'
    #bl_context_mode = 'EDIT'
    bl_idname = "tool.smart_click_poly"
    bl_label = "Smart Click Poly"
    bl_description = ("Select By Angle/ Shift - extend selection")          
    #bl_icon =  "ops.view3d.ruler"          
    bl_icon =  "brush.paint_texture.mask"          
    bl_widget = None 
    bl_keymap = (
        ("view3d.smart_click_poly", {"type": 'LEFTMOUSE', "value": 'PRESS'},
         {"properties": [("extend", False )]}),
        ("view3d.smart_click_poly", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
         {"properties": [("extend", True )]}),
        #("view3d.select_by_angle", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
        # {"properties": [("toggle", True)]}), 3      
         
    )   
    
    
    
    def draw_settings(context, layout, tool):
        props = tool.operator_properties("view3d.smart_click_poly")
        layout.prop(props, "mode", icon='RESTRICT_SELECT_OFF') 
        if props.mode=='ANGLE':                
            layout.prop(props, "angle_value")
        if props.action=='NONE':                    
            layout.prop(props, "action", icon='RADIOBUT_OFF')             
        if props.action=='MAT':                    
            layout.prop(props, "action", icon='MATERIAL')
        if props.action=='CLR':                    
            layout.prop(props, "action", icon='TRASH')
        if props.action=='SEP':                    
            layout.prop(props, "action", icon='MESH_DATA')
        if props.action=='SHRP':                    
            layout.prop(props, "action", icon='SHARPCURVE')
        if props.action=='SEAM':                    
            layout.prop(props, "action", icon='UV')                        
                                                       
        
            
    
class OP_Smart_Click_Poly(bpy.types.Operator):
    bl_idname = "view3d.smart_click_poly"
    bl_label = "Smart Click Poly"
    bl_options = {'REGISTER', 'UNDO'} #
    
    mode_items = [
        ("ANGLE", "Angle", "", 1),
        ("NORMAL", "Island", "", 3),
        ("MATERIAL", "Material", "", 4),
        ("SEAM", "Seam", "", 5),
        ("SHARP", "Sharp", "", 6),
        ("UV", "UVs", "", 7),
    ]    

    action_items = [
        ("NONE", "None", "", 1),
        ("MAT", "Fill Mat", "Fill Active Material", 2),
        ("CLR", "Clear", "Clear Polygons", 3),
        ("SEP", "Separate", "Separate Selected", 4),
        ("SHRP", "Sharp", "Mark Sharp Selected", 5),
        ("SEAM", "Seam", "Mark Seam Selected", 6),
    ] 
     
    angle_value : FloatProperty(name='Angle',subtype='ANGLE', default=0.27)
    action : EnumProperty(items=action_items, name='Action', description='Action After Selection')
    mode : EnumProperty(items=mode_items, name='Mode', default='ANGLE')
    #fill_mat : BoolProperty(name='Fill Active Material', default = False, description='Assign Active Material To Selection') 
    #clear : BoolProperty(name='Clear Polygons', default = False, description='Clear Selected Polygons') 
     
    extend : BoolProperty(name='Extend', default = False)
    #deselect : BoolProperty(default = False)
    #toggle : BoolProperty(default = False)
    #center : BoolProperty(default = False)
    #enumerate : BoolProperty(default = False)
    #object : BoolProperty(default = False)
    location : IntVectorProperty(name='Cursor',default = (0,0),subtype ='XYZ', size = 2)
    
    
    @classmethod
    def poll(cls, context):
        return context.active_object.mode=='EDIT' or context.active_object is not None    
    

    def execute(self, context):
          
        mat_index = bpy.context.object.active_material_index       
        
        bpy.ops.view3d.select(
            extend=self.extend, 
            deselect=False, 
            toggle=False, 
            center=False, 
            enumerate=False, 
            object=False, 
            location=(self.location[0] , self.location[1] )
        )  
             
        
        if self.mode=='ANGLE':
            angle = self.angle_value
            bpy.ops.mesh.faces_select_linked_flat(sharpness=angle)
        else:
            bpy.ops.mesh.select_linked(delimit={self.mode})    
        
        if self.action=='MAT' and self.mode!='MATERIAL':
            bpy.context.object.active_material_index = mat_index 
            bpy.ops.object.material_slot_assign()
            
        if self.action=='CLR':
            bpy.ops.mesh.delete(type='FACE')
            
        if self.action=='SEP':
            try:
                bpy.ops.mesh.separate(type='SELECTED')
            except:
                pass   
            
        if self.action=='SHRP':
            bpy.ops.mesh.region_to_loop()
            bpy.ops.mesh.mark_sharp()
            bpy.ops.mesh.select_mode(type='FACE') 
            
        if self.action=='SEAM':
            bpy.ops.mesh.region_to_loop()
            bpy.ops.mesh.mark_seam(clear=False)
            bpy.ops.mesh.select_mode(type='FACE')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
            bpy.ops.uv.average_islands_scale()
            bpy.ops.uv.pack_islands(margin=0.001)
            bpy.ops.mesh.select_all(action='DESELECT')                                     
           
        return {'FINISHED'}

    def invoke(self, context, event):
        if context.space_data.type == 'VIEW_3D':
            self.location[0] = event.mouse_region_x
            self.location[1]  = event.mouse_region_y
            return self.execute(context)
        else:
                self.report({'WARNING'}, "Active space must be a View3d")
                return {'CANCELLED'} 
                            
                        


bpy.types.Scene.select_type = bpy.props.EnumProperty(name='', items=return_items(), default='COLLECTION')
bpy.types.Scene.select_bool = bpy.props.EnumProperty(name='', items=bool_items())
bpy.types.Scene.select_bbv = bpy.props.EnumProperty(name='', items=bool_items_bbv())


            
def register():
    bpy.utils.register_class(OP_Smart_Click_Poly)
    bpy.utils.register_tool(OT_SMART_CLICK_POLY_TOOL, after={"builtin.select_lasso"}) #, group=True)    
    bpy.utils.register_tool(SmartSelectTool, after={"builtin.select_lasso"}) #, group=True)
    bpy.utils.register_class(OP_SMART_SELECT_TOOL)
    bpy.utils.register_class(OP_SMART_SELECT_TOOL_ADD)
    bpy.utils.register_class(OP_SMART_SELECT_TOOL_SUB)


def unregister():
    bpy.utils.unregister_class(OP_Smart_Click_Poly)    
    bpy.utils.unregister_tool(OT_SMART_CLICK_POLY_TOOL)    
    bpy.utils.unregister_tool(SmartSelectTool) 
    bpy.utils.unregister_class(OP_SMART_SELECT_TOOL)
    bpy.utils.unregister_class(OP_SMART_SELECT_TOOL_ADD)
    bpy.utils.unregister_class(OP_SMART_SELECT_TOOL_SUB)


if __name__ == "__main__":
    register()                