# mesh_select_by_edge_length.py Copyright (C) 2011, Dolf Veenvliet
# Extrude a selection from a mesh multiple times

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  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 2
#  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, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

"""
Usage:
    Launch from from "Select -> By edge length"
    Select all items whose scale/length/surface matches a certain edge length

Additional links:
    Author Site: http://www.macouno.com
    e-mail: dolf {at} macouno {dot} com
"""

import bpy
from bpy.props import (
        FloatProperty,
        BoolProperty,
        EnumProperty,
        )


class Select_by_edge_length():

    # Initialize the class
    def __init__(self, context, edgeLength, edgeSize, extend, space, start_new):

        if start_new:
            bpy.ops.mesh.select_all(action='DESELECT')

        self.ob = context.active_object
        bpy.ops.object.mode_set(mode='OBJECT')

        self.space = space
        self.obMat = self.ob.matrix_world

        bigger = (True if edgeSize == 'BIG' else False)
        smaller = (True if edgeSize == 'SMALL' else False)

        # We ignore vert selections completely
        edgeSelect = bpy.context.tool_settings.mesh_select_mode[1]
        faceSelect = bpy.context.tool_settings.mesh_select_mode[2]

        # Edge select
        if edgeSelect:
            hasSelected = self.hasSelected(self.ob.data.edges)

            for e in self.ob.data.edges:

                if self.selectCheck(e.select, hasSelected, extend):

                    lene = self.getEdgeLength(e.vertices)

                    if (lene == edgeLength or (bigger and lene >= edgeLength) or
                      (smaller and lene <= edgeLength)):
                        e.select = True

                if self.deselectCheck(e.select, hasSelected, extend):
                    lene = self.getEdgeLength(e.vertices)

                    if (lene != edgeLength and not (bigger and lene >= edgeLength) and
                       not (smaller and lene <= edgeLength)):
                        e.select = False

        # Face select
        if faceSelect:
            hasSelected = self.hasSelected(self.ob.data.polygons)

            # Loop through all the given faces
            for f in self.ob.data.polygons:

                # Check if the faces match any of the directions
                if self.selectCheck(f.select, hasSelected, extend):

                    mine, maxe = 0.0, 0.0

                    for i, e in enumerate(f.edge_keys):
                        lene = self.getEdgeLength(e)
                        if not i:
                            mine = lene
                            maxe = lene
                        elif lene < mine:
                            mine = lene
                        elif lene > maxe:
                            maxe = lene

                    if ((mine == edgeLength and maxe == edgeLength) or
                       (bigger and mine >= edgeLength) or
                       (smaller and maxe <= edgeLength)):

                        f.select = True

                if self.deselectCheck(f.select, hasSelected, extend):

                    mine, maxe = 0.0, 0.0

                    for i, e in enumerate(f.edge_keys):
                        lene = self.getEdgeLength(e)
                        if not i:
                            mine = lene
                            maxe = lene
                        elif lene < mine:
                            mine = lene
                        elif lene > maxe:
                            maxe = lene

                    if ((mine != edgeLength and maxe != edgeLength) and
                       not (bigger and mine >= edgeLength) and
                       not (smaller and maxe <= edgeLength)):

                        f.select = False

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

    # Get the length of an edge, by giving this function all verts (2) in the edge
    def getEdgeLength(self, verts):

        vec1 = self.ob.data.vertices[verts[0]].co
        vec2 = self.ob.data.vertices[verts[1]].co

        vec = vec1 - vec2

        if self.space == 'GLO':
            vec = self.obMat * vec

        return round(vec.length, 5)

    # See if the current item should be selected or not
    def selectCheck(self, isSelected, hasSelected, extend):

        # If the current item is not selected we may want to select
        if not isSelected:

            # If we are extending or nothing is selected we want to select
            if extend or not hasSelected:
                return True

        return False

    # See if the current item should be deselected or not
    def deselectCheck(self, isSelected, hasSelected, extend):

        # If the current item is selected we may want to deselect
        if isSelected:

            # If something is selected and we're not extending we want to deselect
            if hasSelected and not extend:
                return True

        return False

    # See if there is at least one selected item
    def hasSelected(self, items):

        for item in items:
            if item.select:
                return True

        return False


class Select_init(bpy.types.Operator):
    bl_idname = "mesh.select_by_edge_length"
    bl_label = "Select by edge length"
    bl_description = ("Select all items whose scale/length/surface matches a certain edge length \n"
                      "Does not work in Vertex Select mode")
    bl_options = {'REGISTER', 'UNDO'}

    edgeLength: FloatProperty(
            name="Edge length",
            description="The comparison scale in Blender units",
            default=1.0,
            min=0.0, max=1000.0,
            soft_min=0.0, soft_max=100.0,
            step=100,
            precision=2
            )
    # Changed to Enum as two separate Booleans didn't make much sense
    sizes = (('SMALL', 'Smaller', "Select items smaller or equal the size setting"),
             ('BIG', 'Bigger', "Select items bigger or equal to the size setting"),
             ('EQUAL', 'Equal', "Select edges equal to the size setting"))
    edgeSize: EnumProperty(
            items=sizes,
            name="Edge comparison",
            description="Choose the relation to set edge length",
            default='EQUAL'
            )
    extend: BoolProperty(
            name="Extend",
            description="Extend the current selection",
            default=False
            )
    start_new: BoolProperty(
            name="Fresh Start",
            default=False,
            description="Start from no previous selection"
            )
    # The spaces we use
    spaces = (('LOC', 'Local', "Use Local space"),
              ('GLO', 'Global', "Use Global Space"))
    space: EnumProperty(
            items=spaces,
            name="Space",
            description="The space to interpret the directions in",
            default='LOC'
            )

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH' and not bpy.context.tool_settings.mesh_select_mode[0])

    def execute(self, context):
        Select_by_edge_length(context, self.edgeLength, self.edgeSize,
                              self.extend, self.space, self.start_new)

        return {'FINISHED'}
