"""
HB_Paste V1.0

Last Modified: 6/25/2015
Works with CINEMA 4D R16.050 and up.
Copyright: Holger Biebrach, www.c4dstuff.com
Code Snippets from: http://www.plugincafe.com/forum/forum_posts.asp?TID=11019&KW=mouse+coordinates&PID=43433#43433
Thanks to Andreas Block for Support.

Name-US: HB_Paste

Description-US: Paste Objects at Mouseposition and Aligns to Surfacenormal (more details in Script)

Usage:
You should allways use HB_Paste with a Shortcut. I set CTRL + SHIFT + V as the default.
If you have copied an Object HB_Paste will paste it at the Position of your Mouse and aligns it to the Surface.
The new Pasted Object will be a child of the selected Object.
!!!! So it is important to take care about which object you select before pasting.!!!
If you have nothing selected the pasted object will be insterted at the begining of you objecthierachy.
If you have your Mouse in empty Space the Object will be pasted at its original Position.
If you have a Spline Selected HB_Paste will make a Mograph Setup and aligns the Pasted Object to the Spline

Video Tutorial:
https://youtu.be/eEcAttV56kE?t=3m50s



Name-DE: HB_Paste
Description-DE: Fügt Objekte bei der Mausposition ein und richtet es nach der Polygonnormale aus. (mehr Details im Script)


ChangeLog:

6/11/2015 Release Version

"""
import c4d, sys, math
from c4d import gui
from c4d import utils

def GetFirstActiveObject(objlist):
    for obj in objlist:
        if obj.GetBit(c4d.BIT_ACTIVE): return obj

def SetGlobalRotation(obj, rot):
   
    m = obj.GetMg()
    pos = m.off
    scale = c4d.Vector( m.v1.GetLength(),
                        m.v2.GetLength(),
                        m.v3.GetLength())
    
    Rot90=(c4d.Vector(rot.x,rot.y+math.pi/2,rot.z+math.pi))
    m = utils.HPBToMatrix(Rot90)
  
    m.off = pos
    m.v1 = m.v1.GetNormalized() * scale.x
    m.v2 = m.v2.GetNormalized() * scale.y
    m.v3 = m.v3.GetNormalized() * scale.z
    
  
    obj.SetMg(m)


def CalcFaceNormal(polyobj, cpoly):
    # Code from: littledevil and NiklasR in http://www.plugincafe.com/forum/forum_posts.asp?TID=8007
    points = polyobj.GetAllPoints()
    p1, p2, p4 = points[cpoly.a], points[cpoly.b], points[cpoly.d]
    nrm = (p2 - p1).Cross(p4 - p1).GetNormalized()
    return nrm


def GetNearestPoly(vpsel, polyobj, width, height, bd, mouse_x, mouse_y):
    vpsel.Init(width, height, bd, [polyobj], c4d.Mpolygons, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL)
    return vpsel.GetNearestPolygon(polyobj, mouse_x, mouse_y, 10) # max_rad may need to be adjusted to needs, without max_rad, the z-distance check may fail on clones

def FindNearestPoly(vpsel, picked_objs, obj_active, width, height, bd, mouse_x, mouse_y, zMin = sys.float_info.max):

    np_result = None

    if picked_objs is not None:
        for obj in picked_objs:
            if obj is None:
                continue

            # if the active object got hit by mouseclick, we want to do nothing
            if obj == obj_active:
                continue

            # polygon objects may have a deform cache
            if obj.GetType() == c4d.Opolygon:

                deformcache = obj.GetDeformCache()
                if deformcache is not None:
                    
                    np = GetNearestPoly(vpsel, deformcache, width, height, bd, mouse_x, mouse_y)
                else:
                    np = GetNearestPoly(vpsel, obj, width, height, bd, mouse_x, mouse_y)
                if np is not None:
                    if np["z"] < zMin:
                        zMin = np["z"]
                        np_result = np
                continue

            # at this point we may have virtually any type of object(-hierarchy), except polygonal objects
            # we don't care for the actual hierarchy, but directly want use the cache
            cache = obj.GetCache()
            if (cache is None):

                # cache not available Cache will be crated
                settings = c4d.BaseContainer()                 # Settings
                settings[c4d.MDATA_CURRENTSTATETOOBJECT_INHERITANCE] = True
                
                point_obj_list = c4d.utils.SendModelingCommand(
                        command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                        list = [obj.GetClone()],
                        mode = c4d.MODELINGCOMMANDMODE_ALL,
                        bc=settings,
                        doc = obj.GetDocument())
                        
                if point_obj_list == False:
                    continue

                np = FindNearestPoly(vpsel, point_obj_list, obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                if np is not None:
                    zMin = np["z"]
                    np_result = np
            else:
                # the cache may be a hierarchy
                np = FindNearestPoly(vpsel, [cache], obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                if np is not None:
                    zMin = np["z"]
                    np_result = np
                np = FindNearestPoly(vpsel, cache.GetChildren(), obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                if np is not None:
                    zMin = np["z"]
                    np_result = np
                np = FindNearestPoly(vpsel, [cache.GetNext()], obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                if np is not None:
                    zMin = np["z"]
                    np_result = np
    return np_result

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

def main():
    
    bd = doc.GetActiveBaseDraw()
    bc = c4d.BaseContainer()
        
    SelectedObjs = doc.GetSelection()  
    FirstSelectedObj=GetFirstActiveObject(SelectedObjs)

    if FirstSelectedObj:
     
        if FirstSelectedObj.GetType()== c4d.Ospline:   # If Selected Object is Spline make Clonersetup
            
            c4d.CallCommand(1018544) # Cloner
            Cloner=doc.GetActiveObject()
            c4d.CallCommand(12108)                         # Paste
            PastedObj=doc.GetActiveObject()
            
            PastedObj.InsertUnder(Cloner)
                
            
            Cloner[c4d.ID_MG_MOTIONGENERATOR_MODE]=0
            Cloner[c4d.MG_OBJECT_LINK]= RootObj
            PastedObj.SetRelPos(c4d.Vector(0,0,0))
            PastedObj.SetRelRot(c4d.Vector(0,0,0))
            Cloner[c4d.MG_SPLINE_MODE]=2
            Cloner[c4d.MG_OBJECT_ALIGN]=True
            Clone[c4d.MG_SPLINE_COUNT]=30
            return
       
    c4d.CallCommand(12108)                         # Paste
    PastedObj=doc.GetActiveObject()

    gui.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSELEFT, bc)
    mouse_x=bc.GetInt32(c4d.BFM_INPUT_X)
    mouse_y=bc.GetInt32(c4d.BFM_INPUT_Y)
    
    # Convert MouseCoordinates to Viewport Coordinates
    win= bd.GetEditorWindow()
    Editor_x, Editor_y = win.Global2Local()        
    mouse_x = mouse_x-abs(Editor_x)
    mouse_y = mouse_y-abs(Editor_y)

    ViewportSelect = c4d.utils.ViewportSelect()
    pick_objects = ViewportSelect.PickObject(bd, doc, mouse_x, mouse_y, rad=0, flags=c4d.VIEWPORT_PICK_FLAGS_0)
    
    frame = bd.GetFrame()
    left = frame["cl"]
    right = frame["cr"]
    top = frame["ct"]
    bottom = frame["cb"]
    width = right - left + 1
    height = bottom - top +1
    
        # in pick_objects we now have a list of all objects that were hit by the mouse click
        # these objects may very well be for exaple cloners, where a clone got hit
    
    nearest_poly = FindNearestPoly(ViewportSelect, pick_objects, PastedObj, width, height, bd, mouse_x, mouse_y)
    if nearest_poly is not None:
        # if we found a polygon to place the object on:
        #   - get camera coordinates for the hit point and convert into world coordinates => position of object
        #   - get normal vector and translate with global matrix of the surface object => rotation of the object
        camCoord = ViewportSelect.GetCameraCoordinates(mouse_x, mouse_y, nearest_poly["z"])
        pos = bd.CW(camCoord)
        surface_obj = nearest_poly["op"]
        poly = surface_obj.GetPolygon(nearest_poly["i"])
        vec_face_normal = CalcFaceNormal(surface_obj, poly)
        mg_surface_obj = surface_obj.GetMg().GetTensorMatrix()
        rot = c4d.utils.VectorToHPB(vec_face_normal * mg_surface_obj) 
        
        if FirstSelectedObj:
            if FirstSelectedObj.GetType()==c4d.Osds:
                FirstSelectedObj=FirstSelectedObj.GetDown()

            FirstSelectedObj_Mg = FirstSelectedObj.GetMg()
            NewPos= ~FirstSelectedObj_Mg * pos
            PastedObj.SetAbsPos(NewPos)
            
            PastedObj.InsertUnder(FirstSelectedObj)
            SetGlobalRotation(PastedObj, rot)
            PastedObj.DelBit(c4d.BIT_ACTIVE)
            FirstSelectedObj.SetBit(c4d.BIT_ACTIVE)
            return
                
        else:
            
            PastedObj.SetAbsPos(pos)
            PastedObj.SetAbsRot(c4d.Vector(rot.x,rot.y+math.pi/2,rot.z+math.pi))
            PastedObj.DelBit(c4d.BIT_ACTIVE)
       
            
        c4d.EventAdd()
    
 
    
if __name__=='__main__':
    main()
