#POLYSWEEPER

bl_info = {
    "name": "POLYSWEEPER",
    "author": "Andrea Rastelli",
    "version": (0, 0, 0),
    "blender": (2, 79, 0),
    "location": "View3D > Tools > Mesh Tools",
    "description": "Polygon sweeper",
    "warning": "working in progress",
    "wiki_url": "",
    "category": "Mesh",
}


import bpy
import bmesh

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

class polysweeper(bpy.types.Operator):
    bl_idname = "mesh.polysweeper"
    bl_label = "polysweeper"
    bl_options = {'REGISTER', 'UNDO'}

    UserValue = FloatProperty(name="Normal Scale", default=1.0)
    sweep = BoolProperty(name="Sweep Faces", default=True)
    FaceNormal = BoolProperty(name="Use Face Normals", default=True)

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        box.label("polysweeper:")

        box.prop(self, "UserValue")
        box.prop(self, "sweep")
        box.prop(self, "FaceNormal")
        
        
    def invoke(self, context, event):
        self.action(context)
        return {'FINISHED'}

    def execute(self, context):
        self.action(context)
        return {'FINISHED'}

    def action(self, context):
        save_global_undo = bpy.context.user_preferences.edit.use_global_undo
        bpy.context.user_preferences.edit.use_global_undo = False
        
        PS(self.UserValue,self.sweep,self.FaceNormal)
        
        bpy.context.user_preferences.edit.use_global_undo = save_global_undo
        bpy.ops.object.editmode_toggle()
        bpy.ops.object.editmode_toggle()

def panel_func(self, context):
    self.layout.label(text="polysweeper:")
    self.layout.operator("mesh.polysweeper", text="polysweeper")

def register():
    bpy.utils.register_class(polysweeper)
    bpy.types.VIEW3D_PT_tools_meshedit.append(panel_func)

def unregister():
    bpy.utils.unregister_class(polysweeper)
    bpy.types.VIEW3D_PT_tools_meshedit.remove(panel_func)


if __name__ == "__main__":
    register()


#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

def PS(userValue,sweep,FaceNormal):
    UV=userValue
    obj = bpy.context.object
    me = obj.data

    CURSOR = bpy.context.scene.cursor_location # cursor location to get correct edge loop
    bm= bmesh.from_edit_mesh(me)
    S = bmesh.from_edit_mesh(me).select_history
    PROFILE = S.active # PROFILE FACE
    PATH = set(S)#.remove(PROFILE) # PATH FACE LOOP
    PROFILE.select = False

    for f in bm.faces:
        if f.select: PATH.add(f)
    PATH.remove(PROFILE)

    PxMAX=[]; PzMAX=[];
    testX=0
    
    for v in PROFILE.verts:
        PxMAX.append(v.co.x)
         
    PxMIN=min(PxMAX); PxMAX=max(PxMAX)  
    
    for v in PROFILE.verts:
        if (v.co.x==PxMIN):
            PzMAX.append(v.co.z) 
                      
    PzMIN=min(PzMAX); PzMAX=max(PzMAX)        
    Zlen=PzMAX-PzMIN
        
    ET=set() 

    for e in bm.edges:
        if e.select: ET.add(e) #EDGE TOTAL

    E = set()
    ER = set() #edge ring

    bpy.ops.mesh.region_to_loop()
    for e in bm.edges:# PATH BORDER EDGE
        if e.select: E.add(e)
        
    #print (len(E)) 
    OPP_F=[]

    for f in PATH:
        count=0
        for e in f.edges:
            if (e in E): count+=1
        if (count>2): OPP_F.append(f)
      
    if (len(OPP_F)==2):
        for f in OPP_F:
            opp_e = set(f.edges)&E
            alt_v = list(set(f.edges)-opp_e)
            alt_v = alt_v[0].verts
            for e in opp_e:
                if not (e.verts[0] in alt_v or e.verts[1] in alt_v):
                    ER.add(e)
                     
    E=E-ER  # LOOP EDGE A+B
    ER = ET-E | ER # EDGE RING

    start_edge= None
    dist=set()

    import mathutils as mat
    print (len(E))
    for e in E:
        mx=(e.verts[0].co.x + e.verts[1].co.x)/2
        my=(e.verts[0].co.y + e.verts[1].co.y)/2
        mz=(e.verts[0].co.z + e.verts[1].co.z)/2
        median= mat.Vector((mx,my,mz))
        dist.add((CURSOR - median).length)

    dist=min(dist) 
    edge_A=None
    for e in (E):
        mx=(e.verts[0].co.x + e.verts[1].co.x)/2
        my=(e.verts[0].co.y + e.verts[1].co.y)/2
        mz=(e.verts[0].co.z + e.verts[1].co.z)/2
        median= mat.Vector((mx,my,mz))
        if ((CURSOR - median).length != dist):
            e.select=False
        else:
            edge_A=e #A_LOOP START

    A_LOOP=set()
    A_VERTS=set()

    def grow_loop(edge):
        A_LOOP.add(edge)
        for v in edge.verts:
            links=v.link_edges
            for link in links:
                if (link in E and link not in A_LOOP):
                    A_LOOP.add(link)
                    grow_loop(link)
                              
    grow_loop(edge_A)  
    B_LOOP=E-A_LOOP   

    for e in A_LOOP:
        for v in e.verts: A_VERTS.add(v)  

    for e in ET:
        e.select=False
        
    import mathutils as m

    SWEEP=set()#sweeped geometry

    for e in ER:
      
        for v in e.verts: 
            if v in A_VERTS: V0=v
            else: V1 = v
        
        PROJ = V1.co-V0.co
        L = PROJ.length
        L= L/Zlen# L:x=Zlen:1
        NU_VERTS=[]
        NU_FACES=[]
        PROJ.normalize()
        S=V0.calc_shell_factor()
        
        if (FaceNormal):
            normal=[]
            for face in e.link_faces:
                #if face in PATH:
                normal.append(face.normal)
            if len(normal)==1:
                normal=normal[0]
            else:
                normal=[(normal[0][0]+normal[1][0])/2,(normal[0][1]+normal[1][1])/2,(normal[0][2]+normal[1][2])/2]   
        else:
            normal=[V0.normal[0],V0.normal[1],V0.normal[2]]
                
        for v in PROFILE.verts:
            
            z0=v.co.z-PzMAX
            x0=v.co.x-PxMIN
            X=V0.co.x - ( PROJ[0]*z0*L) + ( normal[0]*x0*L*S*UV)
            Y=V0.co.y - ( PROJ[1]*z0*L) + ( normal[1]*x0*L*S*UV)
            Z=V0.co.z - ( PROJ[2]*z0*L) + ( normal[2]*x0*L*S*UV)

            v = bm.verts.new( (X,Y,Z) ) 
            NU_VERTS.append(v)
            SWEEP.add(v)
            
        f=bm.faces.new(NU_VERTS)
        f.select=True

    if (sweep==True):
               
        bpy.ops.mesh.delete(type='ONLY_FACE') 
        bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='VERT')
        
        for v in SWEEP:
            v.select=True
        bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
        bpy.ops.mesh.bridge_edge_loops()
        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
    
    bmesh.update_edit_mesh(me, True) 
           
