"""
HB_MakeQuads V1.0

Last Modified: 6/28/2017
Works with CINEMA 4D R16.050 and up.
Copyright: Holger Biebrach, www.c4dstuff.com

Name-US: HB_MakeQuads
Description-US: Make Quads from selection or Close Caps  [POLYGONMODE: Make Quads, EDGEMODE: Close selected hole]

Usage:
You need to select a Polygonobject and make a Polygonselection or Edgeselection
Edgeselection:
Select the boundry Edges from a Polygonhole. The Script will now close the hole with quads. 
(Important: Only works with a even number of selected Edges)

Polygonselection:
The Script will try to fix the topology and make quads from the selected polygons
This does not work in all situations. Please watch Tutorialvideos (Link below)

To change the direction of the Quadstrips you need to select a vertice on the boundry of your selection.


Video Tutorial:
https://youtu.be/G9XVCcOe6jQ?t=14m31s


ChangeLog:

6/28/2017 v1.0 Release Version

"""
import c4d
from c4d import utils,gui
    
def ReverseNormals(obj):
    c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_REVERSENORMALS,
                            list = [obj],
                            mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                            bc = c4d.BaseContainer(),
                            doc = doc)

def NormalMove(obj):
    

    NMsettings = c4d.BaseContainer()                 # Settings
    NMsettings[c4d.MDATA_NORMALMOVE_VALUE] = -0.0001     

    utils.SendModelingCommand(command = c4d.ID_MODELING_NORMALMOVE_TOOL,
                                    list = [obj],
                                    mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                                    bc = NMsettings,
                                    doc = doc)
          


def checktype(tags): # List of current Tag Types on Object
    checktypes = []
    for tag in tags:
        checktypes.append(tag.GetType())
    return checktypes

def GetOldPhong(obj,tagtypes): # Scan Tags on Object
    global OldPhong
    n       = 1 
    
    tags    = obj.GetTags()

    for tag in tags:
        if not tag: continue
        tagtype = tag.GetType()
        count   = checktype(tags).count(tagtype)       
        
        
        if tagtype==5612:
            OldPhong=tag[c4d.PHONGTAG_PHONG_ANGLE]
            return OldPhong
    
    
    

def SetOldPhong(obj,tagtypes): # Scan Tags on Object
    global OldPhong
    n       = 1 
    
    tags    = obj.GetTags()

    for tag in tags:
        if not tag: continue
        tagtype = tag.GetType()
        count   = checktype(tags).count(tagtype)       
        
        
        if tagtype==5612:
            
            
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
            tag[c4d.PHONGTAG_PHONG_ANGLE]=OldPhong
            
            c4d.EventAdd()


def RemoveChildren(objlist):
    
    for obj in objlist:
        doc.AddUndo(c4d.UNDOTYPE_DELETE, obj)
        obj.Remove()

    c4d.EventAdd()    
    return

def InsertChildren(objlist,root):
    
    
    for obj in objlist:
        
        obj.InsertUnder(root)
        doc.AddUndo(c4d.UNDOTYPE_NEW, obj)
        
        
    c4d.EventAdd()    
    return


def ConnectAndDelete(ObjA,ObjB):
    ConnectObj=c4d.BaseObject(1011010)
    ConnectObj[c4d.CONNECTOBJECT_TOLERANCE]=0.001
    ConnectObj.InsertBefore(ObjA)
    doc.AddUndo(c4d.UNDOTYPE_NEW, ConnectObj)
    ObjA.InsertUnder(ConnectObj)
    doc.AddUndo(c4d.UNDOTYPE_NEW, ObjA)
    ObjB.InsertUnder(ConnectObj)
    doc.AddUndo(c4d.UNDOTYPE_NEW, ObjB)
        
    CSsettings = c4d.BaseContainer()                 
    NewObj=utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [ConnectObj],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = CSsettings,
                                    doc = doc,
                                    )
    
    # New Root is a NULL Object
    if NewObj[0].GetType() == c4d.Onull:

        NewPolyObj=NewObj[0].GetDown()
        NewPolyObj.InsertAfter(ConnectObj)
        doc.AddUndo(c4d.UNDOTYPE_NEW, NewPolyObj)
        doc.AddUndo(c4d.UNDOTYPE_DELETE,NewObj[0])
        NewObj[0].Remove()
        doc.AddUndo(c4d.UNDOTYPE_DELETE, ConnectObj)
        ConnectObj.Remove()
        return NewPolyObj
    
    else:
        NewObj[0].InsertAfter(ConnectObj)
        doc.AddUndo(c4d.UNDOTYPE_NEW, NewObj[0])
        doc.AddUndo(c4d.UNDOTYPE_DELETE, ConnectObj)
        ConnectObj.Remove()
        return NewObj[0]


def InvertPolygonSelection(obj):
    selection = obj.GetPolygonS()
    poly_count = obj.GetPolygonCount()

    for i in range(poly_count):
        if selection.IsSelected(i):
            
            selection.Deselect(i)
        else:
            selection.Select(i)
        
    return

def SelectAllPoints(obj):
    selection = obj.GetPointS()
    selection.DeselectAll()
    point_count = obj.GetPointCount()

    for i in range(point_count):
        selection.Select(i)

    return

def SelectAllPolygons(obj):
    selection = obj.GetPolygonS()
    selection.DeselectAll()
    poly_count = obj.GetPolygonCount()

    for i in range(poly_count):
        selection.Select(i)

    return



def GetSelectedPointIDs(PointObj):
    if PointObj is None:
        c4d.gui.MessageDialog("Please Select one Polygonal Object")
        return
    elif not PointObj.CheckType(c4d.Opoint):
        c4d.gui.MessageDialog("Please Select one Polygonal Object")
        return
    else:
        listy=[]
        maxEl=PointObj.GetPointCount()
        bs=PointObj.GetPointS()
        for index, selected in enumerate(bs.GetAll(maxEl)):
            if not selected: continue
             
            else:
                listy.append(index)
                 
        return listy



def GetNearestPoint(splineObj,SelSplinePointpos):
    matr = splineObj.GetMg()
    PCount = splineObj.GetPointCount()
    
    NewDistnace = 10000000000000
    
    for i in range(0,PCount):
        Pointpos = splineObj.GetPoint(i)
        OldDistance=(Pointpos-SelSplinePointpos).GetLength() 


        if OldDistance <= NewDistnace:
            NewDistnace=OldDistance
            MinPointID=i
            
    return MinPointID


def findOutline(obj):
	nbr = utils.Neighbor()
	nbr.Init(obj)
	vcnt = obj.GetPolygonCount()
	vadr = obj.GetAllPolygons()
	sel = obj.GetEdgeS()
	sel.DeselectAll()
	for i in range(0,vcnt):
		pa = vadr[i].a
		pb = vadr[i].b
		pc = vadr[i].c
		pd = vadr[i].d
		if nbr.GetNeighbor(pa, pb, i) == c4d.NOTOK:
			sel.Select(i * 4)
		if nbr.GetNeighbor(pb, pc, i) == c4d.NOTOK:
			sel.Select(i * 4 + 1)
		if pc != pd and nbr.GetNeighbor(pc, pd, i) == c4d.NOTOK:
			sel.Select(i * 4 + 2)
		if nbr.GetNeighbor(pd, pa, i) == c4d.NOTOK:
			sel.Select(i * 4 + 3)




def deletetags(obj): # Scan Tags on Object
    
    n       = 1 
    
    tags    = obj.GetTags()

    for tag in tags:
        if not tag: continue
        tagtype = tag.GetType()
   
        
        if tag.GetName()=="tempsel":
            doc.AddUndo(c4d.UNDOTYPE_DELETE, tag)
            tag.Remove()
            c4d.EventAdd()


############################################
########################## MAIN ############


def main():
    global OldPhong
    doc.StartUndo()
    objlist=doc.GetSelection()
    selobj=objlist[0]
    selobjChildren = selobj.GetChildren()
    RemoveChildren(selobjChildren)
    selobjName=selobj.GetName()
    selobjPos=selobj.GetMg()
    
    tagtypes = []
    OldPhong=GetOldPhong(selobj,tagtypes)
    

    if doc.GetMode() == 7: #Is Polymode??
        
        #SHRINKWRAP
        ObjCopy=selobj.GetClone()
        ObjCopy.InsertAfter(selobj)
        doc.AddUndo(c4d.UNDOTYPE_NEW, ObjCopy)
        
        
        NormalMove(selobj)
        
   
        shrinkwrapdeformer=c4d.BaseObject(1019774)
        shrinkwrapdeformer[c4d.SHRINKWRAP_TARGETOBJECT]=ObjCopy
        shrinkwrapdeformer[c4d.SHRINKWRAP_MAXDISTANCE]=100000
        shrinkwrapdeformer[c4d.SHRINKWRAP_MODE]=0
        shrinkwrapdeformer.InsertUnder(selobj)
        doc.AddUndo(c4d.UNDOTYPE_NEW, shrinkwrapdeformer)
        shrinkwrapdeformerCopy=shrinkwrapdeformer.GetClone()
        
        #### OutlineSelection
        
        settings = c4d.BaseContainer()                 # Settings
        settings[c4d.MDATA_OUTLINESEL_SELECTION]=True
        settings[c4d.MDATA_CONVERTSELECTION_LEFT]=2
        settings[c4d.MDATA_CONVERTSELECTION_RIGHT]=1
        
        utils.SendModelingCommand(command = c4d.MCOMMAND_CONVERTSELECTION,
                                list = [selobj],
                                mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                                bc = settings,
                                doc = doc,
                                flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)

        ### Delete Polygons
        c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_DELETE,
                                  list=[selobj],
                                  mode=c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                                  doc=doc,
                                  flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
        
    
    #### Edge To Spline
    settings = c4d.BaseContainer()               

    utils.SendModelingCommand(command = c4d.MCOMMAND_EDGE_TO_SPLINE,
                                    list = [selobj],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = settings,
                                    doc = doc,
                                    flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
    
    
    
    
    selobjSpline= selobj.GetDown()

    if selobjSpline.GetType()!=c4d.Ospline:

        doc.AddUndo(c4d.UNDOTYPE_DELETE, shrinkwrapdeformer)
        shrinkwrapdeformer.Remove()
        doc.AddUndo(c4d.UNDOTYPE_DELETE, ObjCopy)
        ObjCopy.Remove()
        gui.MessageDialog("Polygonselection can not contain Border Edges.")
        return
    

    pointCount=selobjSpline.GetPointCount()
    if (pointCount % 2) != 0:
        gui.MessageDialog("Uneven number of bounding Edges.")
        selobjSpline.Remove()
        
        if doc.GetMode() == 7: #Is Polymode??
            doc.AddUndo(c4d.UNDOTYPE_DELETE, selobj)
            InsertChildren(selobjChildren,ObjCopy)
            selobj.Remove()
        
        return
    
    
    Selpoint=GetSelectedPointIDs(selobj)
  
    if Selpoint:
        SelPointPos=selobj.GetPoint(Selpoint[0])
    else:
        SelPointPos=0
        

 # Set First Point of Spline
    
    MinDistPointID=GetNearestPoint(selobjSpline,SelPointPos)
    bs=selobjSpline.GetPointS()
    bsCopy=c4d.BaseSelect()
    bs.CopyTo(bsCopy)
    bsCopy.DeselectAll()
    bsCopy.Select(MinDistPointID)
    bsCopy.CopyTo(bs)
    
    settingsSetFirst = c4d.BaseContainer()                 # Settings
    utils.SendModelingCommand(command = c4d.MCOMMAND_SPLINE_REORDER,
                                list = [selobjSpline],
                                mode = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                bc = settingsSetFirst,
                                doc = doc,
                                flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
    

    ##### CALCULATE SELECTIONCENTER
    
    sel = selobjSpline.GetAllPoints()
    points = [(i, point)
          for i, point in enumerate(selobjSpline.GetAllPoints())]

    if not points:
      return
    
    SplineCenter = sum(point[1] for point in points) / float(len(points))
    v1 = c4d.Vector(1,0,0)
    v2 = c4d.Vector(0,1,0)
    v3 = c4d.Vector(0,0,1)
    SplinePos = c4d.Matrix(SplineCenter,v1,v2,v3)
    PosGlobal = selobjPos * SplinePos
    
    
    ############### QUADMESH 
    
    QuadMesh=c4d.BaseObject(c4d.Oplane)
    
    Segments=int(pointCount/4)
    Segtest = float(pointCount/4)
    XSegments=Segments

    YSegments=Segments
    if pointCount % 4 ==2:
        YSegments=Segments+1
        
    
    QuadMesh[c4d.PRIM_PLANE_SUBW]=XSegments
    QuadMesh[c4d.PRIM_PLANE_SUBH]=YSegments
    
    QuadMesh.SetMg(PosGlobal)
    
    QuadMesh.InsertAfter(selobj)
    doc.AddUndo(c4d.UNDOTYPE_NEW, QuadMesh)
    
    c4d.EventAdd()
    
    ########## CURRENT STATE TO OBJECT
    CSsettings = c4d.BaseContainer()                 
    ConvertedQuadMesh=utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [QuadMesh],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = CSsettings,
                                    doc = doc,
                                    )
                      
    ConvertedQuadMesh[0].InsertAfter(QuadMesh)
    doc.AddUndo(c4d.UNDOTYPE_NEW, ConvertedQuadMesh[0])
    
    doc.AddUndo(c4d.UNDOTYPE_DELETE, QuadMesh) 
    QuadMesh.Remove()

    QuadMesh=ConvertedQuadMesh[0]
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, QuadMesh)
    QuadMesh.SetBit(c4d.BIT_ACTIVE)
    
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, selobj)
    selobj.DelBit(c4d.BIT_ACTIVE)

    findOutline(QuadMesh)

    utils.SendModelingCommand(command = c4d.MCOMMAND_EDGE_TO_SPLINE,
                                    list = [QuadMesh],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = settings,
                                    doc = doc)
    
    OriginalSpline=QuadMesh.GetDown()
    
    
    ##########################################   Set Original Spline First Point
    
    pointCount=OriginalSpline.GetPointCount()
    
    OriginalSplineID=int(round(pointCount/4/2)-1)
    #print "Spline ID point: ", OriginalSplineID
    bs=OriginalSpline.GetPointS()
    bsCopy=c4d.BaseSelect()
    bs.CopyTo(bsCopy)
    bsCopy.DeselectAll()
    
  

    bsCopy.Select(OriginalSplineID)
    bsCopy.CopyTo(bs)
    #print "ID ORIGINAL: ", OriginalSplineID
    
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, OriginalSpline)
    settingsSetFirst = c4d.BaseContainer()                 # Settings
    utils.SendModelingCommand(command = c4d.MCOMMAND_SPLINE_REORDER,
                                list = [OriginalSpline],
                                mode = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                bc = settingsSetFirst,
                                doc = doc,
                                flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
    
                                            
################# SPLINE DEFORMER                              
                      
    SplineDeformer=c4d.BaseObject(c4d.Osplinedeformer)
    OriginalSpline[c4d.SPLINEOBJECT_CLOSED]=True
    selobjSpline[c4d.SPLINEOBJECT_CLOSED]=True
    
    SplineDeformer[c4d.SPLINEDEFORMEROBJECT_ORIGINAL_SPLINE]=OriginalSpline
    SplineDeformer[c4d.SPLINEDEFORMEROBJECT_MODIFY_SPLINE]=selobjSpline
    
    SplineDeformer.InsertUnder(QuadMesh)
    doc.AddUndo(c4d.UNDOTYPE_NEW, SplineDeformer)

    
    ### CURRENT STATE TO OBJECT QUADMESH
    FinishedQuadMesh=utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                list = [QuadMesh],
                                mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                bc = CSsettings,
                                doc = doc,
                                )
    
    FinishedQuadMesh[0].InsertAfter(QuadMesh)
    doc.AddUndo(c4d.UNDOTYPE_NEW, FinishedQuadMesh[0])
    FinishedQuadMesh=FinishedQuadMesh[0]


    doc.AddUndo(c4d.UNDOTYPE_DELETE,QuadMesh)
    QuadMesh.Remove()
    
    doc.AddUndo(c4d.UNDOTYPE_DELETE,selobjSpline)
    selobjSpline.Remove()

    
    
    if FinishedQuadMesh.GetType()==c4d.Onull:
        FinishedQuadMesh=FinishedQuadMesh.GetDown()
        
        FinishedQuadMeshROOT=FinishedQuadMesh.GetUp()
        FinishedQuadMeshPOS=FinishedQuadMeshROOT.GetMg()
        
        doc.AddUndo(c4d.UNDOTYPE_DELETE,FinishedQuadMeshROOT)
        FinishedQuadMeshROOT.Remove()
        
        
        
        FinishedQuadMesh.InsertAfter(selobj)
        doc.AddUndo(c4d.UNDOTYPE_NEW, FinishedQuadMesh)
        
        doc.AddUndo(c4d.UNDOTYPE_CHANGE, FinishedQuadMesh)
        FinishedQuadMesh.SetMg(FinishedQuadMeshPOS)




    ###SMOOTH IT
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, FinishedQuadMesh)
    FinishedQuadMesh.SetBit(c4d.BIT_ACTIVE)
    ReverseNormals(FinishedQuadMesh)

    
    SelectAllPoints(FinishedQuadMesh)


    
    SmoothDeformer=c4d.BaseObject(1024529)
    SmoothDeformer[c4d.ID_CA_SMOOTHING_DEFORMER_OBJECT_STIFFNESS]=0
    SmoothDeformer[c4d.ID_CA_SMOOTHING_DEFORMER_OBJECT_STRENGTH]=500
    SmoothDeformer[c4d.ID_CA_SMOOTHING_DEFORMER_OBJECT_ITERATIONS]=2000

    c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_SELECTSHRINK,
                                list = [FinishedQuadMesh],
                                mode = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                bc = c4d.BaseContainer(),
                                doc = doc)

 ### Set Selection and Restrict
    
    newseltag = FinishedQuadMesh.MakeTag(c4d.Tpointselection)
    newseltag_bs = newseltag.GetBaseSelect()
    FinishedQuadMesh.GetPointS().CopyTo(newseltag_bs)
    
    newseltag.SetName("tempsel")
    
    FinishedQuadMesh.InsertTag(newseltag)
    doc.AddUndo(c4d.UNDOTYPE_NEW, newseltag)

    restag = c4d.BaseTag(5683)
    restag[c4d.RESTRICTIONTAG_NAME_01]="tempsel"

    SmoothDeformer.InsertTag(restag)
    doc.AddUndo(c4d.UNDOTYPE_NEW, restag)
    
    SmoothDeformer.InsertUnder(FinishedQuadMesh)
    doc.AddUndo(c4d.UNDOTYPE_NEW, SmoothDeformer)
    
    ### ADD Another Shrinkwrap if Polymode
    
    if doc.GetMode() == 7: #Is Polymode??
        shrinkwrapdeformerCopy.InsertAfter(SmoothDeformer)
        doc.AddUndo(c4d.UNDOTYPE_NEW, shrinkwrapdeformerCopy)


    CSsettings = c4d.BaseContainer()                 
    NewObj=utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [FinishedQuadMesh],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = CSsettings,
                                    doc = doc
                                    )
    
    NewObj[0].InsertAfter(FinishedQuadMesh)
    doc.AddUndo(c4d.UNDOTYPE_NEW, NewObj[0])
    doc.AddUndo(c4d.UNDOTYPE_DELETE, FinishedQuadMesh)
    FinishedQuadMesh.Remove()

    doc.AddUndo(c4d.UNDOTYPE_CHANGE, selobj)
    selobj.SetBit(c4d.BIT_ACTIVE)
    
    SelectAllPolygons(selobj)
    if doc.GetMode() == 7: #Is Polymode??
        doc.AddUndo(c4d.UNDOTYPE_DELETE, ObjCopy)
        ObjCopy.Remove()

    doc.AddUndo(c4d.UNDOTYPE_CHANGE, NewObj[0])
    NewObj[0].SetBit(c4d.BIT_ACTIVE)
    deletetags(NewObj[0])
    
    newSelObj = ConnectAndDelete(NewObj[0], selobj)
    NewObjChildren = newSelObj.GetChildren()
    RemoveChildren(NewObjChildren)
    
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, newSelObj)
    newSelObj.SetBit(c4d.BIT_ACTIVE)
    InsertChildren(selobjChildren,newSelObj)
    
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, newSelObj)
    newSelObj.SetName(selobjName)
   
    SelectAllPoints(newSelObj)
    settings = c4d.BaseContainer()
    settings[c4d.MDATA_OPTIMIZE_TOLERANCE] =0.001
    settings[c4d.MDATA_OPTIMIZE_POINTS]=True
    settings[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS]=True 

    utils.SendModelingCommand(command = c4d.MCOMMAND_OPTIMIZE,
                                list = [newSelObj],
                                mode = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                bc = settings,
                                doc = doc,
                                flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)

    InvertPolygonSelection(newSelObj)
    
    ###### CONVERT SELECTION
    ConSsettings = c4d.BaseContainer()                 # Settings
    ConSsettings[c4d.MDATA_OUTLINESEL_SELECTION]=True
    ConSsettings[c4d.MDATA_CONVERTSELECTION_LEFT]=2
    ConSsettings[c4d.MDATA_CONVERTSELECTION_RIGHT]=0
    
    utils.SendModelingCommand(command = c4d.MCOMMAND_CONVERTSELECTION,
                            list = [newSelObj],
                            mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                            bc = ConSsettings,
                            doc = doc,
                            flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)


    SetOldPhong(newSelObj,tagtypes)
    c4d.EventAdd()
    doc.EndUndo()
    
        
if __name__=='__main__':
    main()