#!BPY

"""
Name: 'Mesh Copy/Paste'
Blender: 242
Group: 'Mesh'
Tooltip: 'Copy/Paste part of a mesh (beetween Blender instances too).'
"""

__author__  = "Guillaume Englert"
__version__ = "0.1 2006/10/24"
__url__     = "Online doc , http://www.hybird.org/~guiea_7/"
__email__   = "GuieA_7, genglert:hybird*org"
__bpydoc__  = """\
Two operations are possible :<br>
- Copy the selected part of a mesh in a buffer.<br>
- Paste a copy of the buffer.<br>
<br>
Notice that buffer is shared by the instances of Blender (for each user).
"""

################################################################################
#                                                                              #
#    GNU GPL LICENSE                                                           #
#    ---------------                                                           #
#                                                                              #
#    Copyright (C) 2006: Guillaume Englert                                     #
#                                                                              #
#    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.           #
#                                                                              #
################################################################################


################################################################################
# Importing modules
################################################################################

from Blender import Get
from Blender import Mesh, Redraw
from Blender.Draw import PupMenu
from Blender.Mathutils import Vector
from Blender.Object import GetSelected
from Blender.Window import EditMode, GetCursorPos
from Blender.sys import join

from exceptions import Exception
from array import array #array is used instead of 'Pickle' to be secure


################################################################################
#                              EXCEPTIONS                                      #
################################################################################

class MCPFatalError(Exception):
    """Fatal error, like no object selected."""
    def __init__(self, msg):
        Exception.__init__(self)
        self.msg = msg

    def __str__(self):
        return self.msg


################################################################################
#                             CONSTANTS                                        #
################################################################################

TMPNAME = "MCPbuffer"

COPY   = 1
PASTE  = 2
CANCEL = -1

FLOAT_T   = 'f'
INTEGER_T = 'i'


################################################################################
#                             FUNCTIONS                                        #
################################################################################



def display_error(string):
    PupMenu("Error !%t|" + string)

#-------------------------------------------------------------------------------

def get_action():
    return PupMenu("Copy to buffer|"
                   "Paste from buffer")

#-------------------------------------------------------------------------------

#thx to Sam Hocevar, author of monsterz - http://sam.zoy.org
def get_username():
    from sys import platform
    if platform == 'win32':
        from os import environ
        return environ.get('USER') or environ.get('USERNAME') or 'You'

    from pwd import getpwuid
    from os import geteuid
    return getpwuid(geteuid())[0]

#-------------------------------------------------------------------------------

def get_tmpdir():
    try:
        from tempfile import gettempdir
        tmp = gettempdir()
    except ImportError:
        pass

    if not tmp:
        tmp = Get('tempdir')

        if not tmp:
            tmp = Get('datadir')

            if not tmp:
                raise MCPFatalError("no temporary directory|"
                                    "it can be set at the User Preferences window -> File Paths tab.")
    return tmp

#-------------------------------------------------------------------------------

def write_list(fileobj, typ, lst):
    """Write a list of int or float in a file.
    fileobj: an open file object.
    typ: type of objects of the list (INTEGER_T or FLOAT_T)
    lst: the list to write
    """
    #the length of the list is written, then the list data
    buff = array(INTEGER_T, (len(lst),))
    buff.tofile(fileobj)
    buff = array(typ, lst)
    buff.tofile(fileobj)

#-------------------------------------------------------------------------------

def read_list(fileobj, typ):
    """Read a list of int or float from a file.
    fileobj: an open file object.
    typ: type of objects of the list (INTEGER_T or FLOAT_T)
    """
    #the length of the list is read, then the list data
    buff = array(INTEGER_T)
    buff.fromfile(fileobj, 1)
    size = buff.pop()

    buff = array(typ)
    buff.fromfile(fileobj, size)

    return buff.tolist()

#-------------------------------------------------------------------------------

def group(lst, length):
    """A generator which yields 'tuple'(sublist in fact) from a list.
    lst: the list from which elements are taken.
    length: length of tuples which are yields.
    eg: group([1,2,3,4], 2) ---> yields [1,2], then [3,4]
    """
    it = iter(lst)
    for useless in xrange(len(lst)/length):
        yield [it.next() for i in xrange(length)]

#-------------------------------------------------------------------------------

def copy(filename, mesh):
    """Serialize a mesh in a file.
    filename: name of a file which can be open to write.
    mesh: the mesh to 'copy' (Blender.Mesh.Mesh object)"""
    mverts = mesh.verts
    vsel   = mverts.selected()

    if not vsel: return

    #index of vertices are relocated (in order to be consecutive)
    #reloc is a dictionnary with: key==old_index, value==new_index
    reloc = dict((old,new) for new, old in enumerate(vsel))

    #average point
    avg_vect = sum((mverts[i].co for i in vsel), Vector()) * (1.0/len(vsel))

    try:
        tmpfile = file(filename, 'w')
    except IOError:
        raise MCPFatalError("can't open temp file for write(%s)" % filename)

    try:
        #dump vertices (relatively to the average point)
        write_list(tmpfile, FLOAT_T,
                   [coord for i in vsel for coord in (mverts[i].co-avg_vect)])

        #dump edges
        SEL_E = Mesh.EdgeFlags['SELECT']
        write_list(tmpfile, INTEGER_T,
                   [reloc[vert.index] for edge in mesh.edges if (edge.flag & SEL_E) for vert in edge])

        #dump faces
        mfaces = mesh.faces
        listof3vfaces = [] #faces with 3 vertices
        listof4vfaces = [] #faces with 4 vertices
        for i in mfaces.selected():
            face = mfaces[i]
            if len(face) == 3:
                for vert in face: listof3vfaces.append(reloc[vert.index])
            else: #len(face) == 4
                for vert in face: listof4vfaces.append(reloc[vert.index])

        write_list(tmpfile, INTEGER_T, listof3vfaces)
        write_list(tmpfile, INTEGER_T, listof4vfaces)

    except Exception, e:
        print e
        raise MCPFatalError("can't write temp file(%s)" % filename)

    tmpfile.close()


#-------------------------------------------------------------------------------


def paste(filename, mesh, object):
    """Create a serialized mesh from a file.
    filename: name of the file which contains a serialized mesh.
    mesh: the mesh where to paste the new mesh (Blender.Mesh.Mesh object)
    object: the object that owns the mesh data (Blender.Object object)"""
    try:
        tmpfile = file(filename, 'r')
    except:
        raise MCPFatalError("can't open temp file(%s)" % filename)

    try:
        listofverts   = read_list(tmpfile, FLOAT_T)
        listofedges   = read_list(tmpfile, INTEGER_T)
        listof3vfaces = read_list(tmpfile, INTEGER_T) #faces with 3 vertices
        listof4vfaces = read_list(tmpfile, INTEGER_T) #faces with 4 vertices

    except EOFError, e:
        print e
        tmpfile.close()
        raise MCPFatalError("%s is not a valid buffer file" % filename)

    tmpfile.close()

    #get the cursor position in the object space
    cursor = Vector(GetCursorPos())
    cursor.resize4D()
    cursor *= object.getInverseMatrix() #and not object.getInverseMatrix()*cursor
    cursor.resize3D()

    try:
        mverts    = mesh.verts
        idxoffset = len(mverts)

        #paste the vertices (centered around the cursor)
        if listofverts:
            mverts.extend([Vector(tupl)+cursor for tupl in group(listofverts, 3)])

            #paste the edges
            if listofedges:
                mesh.edges.extend([(mverts[i1+idxoffset], mverts[i2+idxoffset]) for i1, i2 in group(listofedges, 2)])

                if listof3vfaces:
                    mesh.faces.extend([[idx+idxoffset for idx in face] for face in group(listof3vfaces, 3)])

                if listof4vfaces:
                    mesh.faces.extend([[idx+idxoffset for idx in face] for face in group(listof4vfaces, 4)])

    except Exception, e:
        print e
        raise MCPFatalError("%s is not a valid buffer file(bad type)" % filename)

    mesh.update()


################################################################################
#                           MAIN FUNCTION                                      #
################################################################################

def main():
    """Da main function ! :)"""
    #init
    is_editmode = EditMode()
    if is_editmode:
        EditMode(0)

    try:
        #get selected object (or quit)
        objs = GetSelected()
        if not objs:
            raise MCPFatalError("none selected object")

        if len(objs) > 1:
            raise MCPFatalError("only one object must be selected")

        obj = objs[0]
        if obj.getType() != "Mesh":
            raise MCPFatalError("active object must be a mesh")


        #do the wanted action on the mesh
        mesh       = obj.getData(mesh=True)
        abstmpname = join(get_tmpdir(), TMPNAME+'-'+get_username())

        #determine action to do
        action = get_action()

        if   action == CANCEL: pass
        elif action == COPY:   copy(abstmpname, mesh)
        elif action == PASTE:  paste(abstmpname, mesh, obj)
        else :
            raise MCPFatalError("bad menu item")

    #Exceptions handlers
    except MCPFatalError, e:
        print e
        display_error(str(e))

    except:
        import sys
        sys.excepthook(*sys.exc_info())
        display_error("An exception occured | (look at the terminal)")


    #finish
    if is_editmode :
        EditMode(1)


################################################################################
#                           MAIN PROGRAM                                       #
################################################################################

main()
