"""
HB_SnapToSpline V1.3

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

Name-US: HB_SnapToSpline
Description-US: Snaps slected Edges to selected Spline [SHIFT: Clone Edges with subdivisions, CTRL: Input Influence Radius]

Usage:
You need to select a Polygonobject and a Spline. Also make an Edgeselection on the Polygonobject. Run the Script.
The Selected Edges will be snapped to the Selected Spline. When you hold Shift the Script will extrude/Clode the Edgeselection and create a Patch between
the selected edges and the Spline (with the subdivisions you set). If you hold CTRL the Script will influence all Points within the Radius you set.
New in V1.1: If you have a Spine with several Segments selected the Script will repeat the process for each spline segement. That is intended for 
Retopo-Work. 

Video Tutorial:
https://youtu.be/Ki6I9sRdeUU?t=25m50s

Name-DE: HB_SnapToSpline
Description-DE: Snapt selektierte Kanten an selektierte Spline [SHIFT: Clone Edges with subdivisions, CTRL: Input Influence Radius]


ChangeLog:

11/28/2016 v1.3
- Fixed a problem with precision
- Fixed a minor Issue with bad results in some cases

11/2/2016 v1.2
- Fixed a Bug where you could not use an Object without Material

1/1/2016 v1.1
- Support for Splines with multiple segments
- Snapspline will get deleted if multisegments
- Retopo Support

6/11/2015 v1.0 Release Version

"""
import c4d
from c4d import utils
from c4d import gui

def DoitOnAllSegments(RootSpline):
    global PolyObj
    global SHIFT
    global CTRL
    global SubDivs
    global SegmentCount
    global NewObj
    global ObjChildren
    global DeformerRadius
    
    
    if SegmentCount>1:
        ChildSpline=RootSpline.GetDown()
    else:
        ChildSpline=RootSpline
        

    

    while ChildSpline:
        
        PolyObj=GetSelectedPolyobj(doc.GetSelection())
        PointPosSelSpline = GetFirstPointPosition(ChildSpline)
        LastPointPosSelSpline = GetLastPointPosition(ChildSpline)
    
        objType=PolyObj.GetType()
        ObjPos=PolyObj.GetMg()
        
        
        if SHIFT==True:
            
            #EXTRUDE
            settingsExtrude = c4d.BaseContainer()                 # Settings Edge to Spline
            settingsExtrude[c4d.MDATA_EXTRUDE_SUBDIVISION]=int(SubDivs)
            settingsExtrude[c4d.MDATA_EXTRUDE_OFFSET]=1
            settingsExtrude[c4d.MDATA_EXTRUDE_EDGEANGLE]=0
            settingsExtrude[c4d.MDATA_EXTRUDE_PRESERVEGROUPS]=True
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, PolyObj)
            utils.SendModelingCommand(command = c4d.ID_MODELING_EXTRUDE_TOOL,
                                    list = [PolyObj],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = settingsExtrude,
                                    doc = doc,
                                    flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
                                    
  
        # EDGE TO SPLINE    
        
        
        settings = c4d.BaseContainer()                 # Settings Edge to Spline
        utils.SendModelingCommand(command = c4d.MCOMMAND_EDGE_TO_SPLINE,
                                    list = [PolyObj],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = settings,
                                    doc = doc,
                                    flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
                                    
        
        # Mospline
        OriginalSpline=PolyObj.GetDown()
        
        # Set First Point
        MinDistPointID=GetNearestPoint(OriginalSpline,PointPosSelSpline)
        
        bs=OriginalSpline.GetPointS()
        bsCopy=c4d.BaseSelect()
        bs.CopyTo(bsCopy)
        bsCopy.DeselectAll()
        bsCopy.Select(MinDistPointID)
        bsCopy.CopyTo(bs)
        
 
        # If Spline is Closed set First Point
        if OriginalSpline[c4d.SPLINEOBJECT_CLOSED] == True:
            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)
            
        
        PointPosEdgeSpline = GetFirstPointPosition(OriginalSpline)
        LastPointPosEdgeSpline = GetLastPointPosition(OriginalSpline)
        
        DistanceFirstPoint=(PointPosSelSpline-PointPosEdgeSpline).GetLength()
        DistanceLastPoint=(PointPosSelSpline-LastPointPosEdgeSpline).GetLength()
        

        #Reverse Order if Distance is greater to last point

        if DistanceFirstPoint > DistanceLastPoint:
            settingsReverse = c4d.BaseContainer()                 # Settings
            utils.SendModelingCommand(command = c4d.MCOMMAND_SPLINE_REVERSE,
                                        list = [OriginalSpline],
                                        mode = c4d.MODELINGCOMMANDMODE_POINTSELECTION,
                                        bc = settingsReverse,
                                        doc = doc,
                                        flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
    
        
        # Mospline SETUP

        
        PointCount=OriginalSpline.GetPointCount()
      
        MoSpline=c4d.BaseObject(440000054)
        MoSpline[c4d.MGMOSPLINEOBJECT_MODE]=1
        MoSpline[c4d.MGMOSPLINEOBJECT_SOURCE_SPLINE]=ChildSpline
        MoSpline[c4d.MGMOSPLINEOBJECT_SPLINE_MODE]=2
        MoSpline[c4d.MGMOSPLINEOBJECT_SPLINE_COUNT]=PointCount*10
        MoSpline[c4d.MGMOSPLINEOBJECT_GROWTH_END]=1
        SetGlobalRotation(MoSpline,c4d.Vector(0,0,0))
        SetGlobalPosition(MoSpline,c4d.Vector(0,0,0))
        MoSpline.InsertBefore(ChildSpline)
        doc.AddUndo(c4d.UNDOTYPE_NEW, MoSpline)

        # SplineDeformer
        SplineDeformer=c4d.BaseObject(1008982)
        SplineDeformer[c4d.SPLINEDEFORMEROBJECT_RADIUS]=0.001
        if SHIFT==True:
            SplineDeformer[c4d.SPLINEDEFORMEROBJECT_RADIUS]=1
        
        
        SplineDeformer[c4d.SPLINEDEFORMEROBJECT_USE_LENGTH]=False
        SplineDeformer[c4d.SPLINEDEFORMEROBJECT_ORIGINAL_SPLINE]=OriginalSpline
       
        SplineDeformer[c4d.SPLINEDEFORMEROBJECT_MODIFY_SPLINE]=MoSpline
        if CTRL == True: 
            SplineDeformer[c4d.SPLINEDEFORMEROBJECT_RADIUS]=float(DeformerRadius)
            SplineDeformer[c4d.SPLINEDEFORMEROBJECT_USE_LENGTH]=False
        
        SplineDeformer.InsertAfter(OriginalSpline)
        doc.AddUndo(c4d.UNDOTYPE_NEW, SplineDeformer)
         
        
        SplineDeformer.Message(c4d.MSG_DESCRIPTION_VALIDATE)
        
        
        CSsettings = c4d.BaseContainer()                 
        NewObj=utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                                    list = [PolyObj],
                                    mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION,
                                    bc = CSsettings,
                                    doc = doc,
                                    )
     
        NewObj[0].SetMg(ObjPos)
        NewObj[0].InsertAfter(PolyObj)
        doc.AddUndo(c4d.UNDOTYPE_NEW, NewObj[0])
        
       
       
        
        doc.AddUndo(c4d.UNDOTYPE_DELETE,MoSpline)
        MoSpline.Remove()
        
       
        
        # New Root is a NULL Object
        if NewObj[0].GetType() == c4d.Onull:

            NewPolyObj=NewObj[0].GetDown()
    
            NewPolyObj.InsertAfter(PolyObj)
            doc.AddUndo(c4d.UNDOTYPE_NEW, NewPolyObj)
            doc.AddUndo(c4d.UNDOTYPE_DELETE,NewObj[0])
            NewObj[0].Remove()
            doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, NewPolyObj)
            doc.SetSelection(NewPolyObj, c4d.SELECTION_ADD)
    
            #InsertChildren(ObjChildren,NewPolyObj)
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, NewPolyObj)
            NewPolyObj.SetMg(ObjPos)
            
            
        else:# New Root is a PolyObject

            NewPolyObj=NewObj[0]
            NewObjChildren= NewObj[0].GetChildren()
            
            
            RemoveChildren(NewObjChildren)
            doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, NewObj[0])
            doc.SetSelection(NewPolyObj, c4d.SELECTION_ADD)

            doc.AddUndo(c4d.UNDOTYPE_CHANGE, NewPolyObj)
            NewPolyObj.SetMg(ObjPos)

           
    
        
        
        doc.AddUndo(c4d.UNDOTYPE_DELETE,PolyObj)
        PolyObj.Remove()
        
        
        
        if ObjChildren:
            
            
            InsertChildren(ObjChildren,NewPolyObj)


        
        if SegmentCount == 0 or SegmentCount == 1:
            
            return
        
        
        ChildSpline = ChildSpline.GetNext()
        
        
        
    
    if RootSpline is None:
        return

    return


def SetGlobalPosition(obj, pos):
    m = obj.GetMg()
    m.off = pos
    obj.SetMg(m)

def SetGlobalRotation(obj, rot):
    m = obj.GetMg()
    pos = m.off
    scale = c4d.Vector( m.v1.GetLength(),
                        m.v2.GetLength(),
                        m.v3.GetLength())

    m = utils.HPBToMatrix(rot)

    m.off = pos
    m.v1 = m.v1.GetNormalized() #* scale.x
    m.v2 = m.v2.GetNormalized() #* scale.y
    m.v3 = m.v3.GetNormalized() #* scale.z
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)
    obj.SetMg(m)

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 GetSelectedSpline(objlist):
    for obj in objlist:
        if obj.GetType()==c4d.Ospline:
            return obj
    return

def GetSelectedPolyobj(objlist):
    for obj in objlist:
        if obj.GetType()==c4d.Opolygon:
            return obj

    return

def GetFirstPointPosition(obj):
    AllPoints=obj.GetAllPoints()  
    FirstPointPos=AllPoints[0]
    matr = obj.GetMg()
    FirstPointPosGlobal = FirstPointPos * matr
    return FirstPointPosGlobal

def GetLastPointPosition(obj):
    AllPoints=obj.GetAllPoints()  
    PCount = obj.GetPointCount()
    LastPointPos=AllPoints[PCount-1]
    matr = obj.GetMg()
    LastPointPosGlobal = LastPointPos * matr
    return LastPointPosGlobal


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


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


##########             MAIN

def main():
    
    global PolyObj
    global SHIFT
    global CTRL
    global SubDivs
    global SegmentCount
    global NewObj
    global ObjChildren
    global DeformerRadius
    
    if len(doc.GetSelection()) != 2:
        gui.MessageDialog("Select one Polygonobject and one Spline!")
        return
    
    
    SelSpline=GetSelectedSpline(doc.GetSelection())
    if not SelSpline:
        gui.MessageDialog("Select one Polygonobject and one Spline!")
        return
    


    PolyObj=GetSelectedPolyobj(doc.GetSelection())
    if not PolyObj:
        gui.MessageDialog("Select one Polygonobject and one Spline!")
        return
    

    
    SHIFT=False
    CTRL=False
    bc = c4d.BaseContainer()
    c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD,c4d.BFM_INPUT_CHANNEL,bc)
           
    if bc[c4d.BFM_INPUT_QUALIFIER] ==1 : #SHIFT: Open Dialog and setup Subdivisions
        SHIFT=True
        SubDivs = gui.InputDialog("Subdivisions",0)
        if not SubDivs: return
    
    if bc[c4d.BFM_INPUT_QUALIFIER] ==2 :
            CTRL=True
            DeformerRadius = gui.InputDialog("Influence Radius")
            if not DeformerRadius: return
    
    SegmentCount=SelSpline.GetSegmentCount() # SEGMENTS?
     
    ObjChildren = PolyObj.GetChildren()

    doc.StartUndo()
    
    RemoveChildren(ObjChildren)
 

    if SegmentCount > 1:
        

        utils.SendModelingCommand(command = c4d.MCOMMAND_EXPLODESEGMENTS,
                                    list = [SelSpline],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    bc =  c4d.BaseContainer(),
                                    doc = doc,
                                    flags = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO)
                                    

       
        RootSpline=GetSelectedSpline(doc.GetSelection())
        

        
        DoitOnAllSegments(RootSpline)
        doc.AddUndo(c4d.UNDOTYPE_DELETE,RootSpline)
        RootSpline.Remove()   
        return
    else:
        
        DoitOnAllSegments(SelSpline)

    

    c4d.EventAdd()
    doc.EndUndo()
    
    
if __name__=='__main__':
    main()
