# QuArK  -  Quake Army Knife
#
# Copyright (C) 1996-2000 Armin Rigo
# THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
# FOUND IN FILE "COPYING.TXT"
#
#$Header: /cvsroot/quark/runtime/plugins/mapdupspath.py,v 1.47 2002/08/04 11:49:24 decker_dk Exp $


Info = {
   "plug-in":       "Path Duplicator",
   "desc":          "Path Duplicator",
   "date":          "3 feb 01",
   "author":        "Decker, also tiglari",
   "author e-mail": "decker@planetquake.com, tiglari@planetquake.com",
   "quark":         "Version 6.2"
}

import quarkx
from quarkpy.maputils import *
import plugins.deckerutils
import quarkpy.mapduplicator
import quarkpy.maphandles
import quarkpy.mapentities
import quarkpy.qhandles
import quarkpy.mapbtns
import quarkpy.dlgclasses
import math
StandardDuplicator = quarkpy.mapduplicator.StandardDuplicator
DuplicatorManager = quarkpy.mapduplicator.DuplicatorManager
DupOffsetHandle = quarkpy.mapduplicator.DupOffsetHandle

from quarkpy.mapentities import ObjectOrigin
from quarkpy.maphandles import GetUserCenter
from quarkpy.maphandles import UserCenterHandle


#
# destructive, do to copies
#
def evaluateDuplicators(group):
    for obj in group.subitems:
       if obj.type==":b" or obj.type==":g":
           evaluateDuplicators(obj)
    getmgr = quarkpy.mapduplicator.DupManager
    for obj in group.subitems:
       if obj.type==":d":
            index=group.subitems.index(obj)
            mgr = getmgr(obj)
            image = 0
            while 1:
                objlist = mgr.buildimages(image)
                if len(objlist)==0:
                    break
                image = image + 1
                new = quarkx.newobj("%s (%d):g" % (obj.shortname, image))
                for o in objlist:
                    new.appenditem(o)
                group.removeitem(index)
                group.insertitem(index, new)

#
#  Two ways of making axes that don't `twist'.  This leads to the
#    section not joining properly, but maybe something can be done
#    about this someday (with curved `elbows', for example)
#
def MakeAxes2(x):
    xn=x.normalized
    if abs(xn*quarkx.vect(0,1,0))<.1:
        z=(xn^quarkx.vect(-1,0,0)).normalized
    else:
        z=(xn^quarkx.vect(0,1,0)).normalized
    y = (z^x).normalized
    return xn, y, z

def Vertical(x):
    x=x.normalized
    return abs(x*quarkx.vect(0,0,1))>.99999

def MakeLevelAxes(x):
    x=x.normalized
    mapx, mapy, mapz = quarkx.vect(1,0,0),quarkx.vect(0,1,0),quarkx.vect(0,0,1)
    dot = x*mapz
    #
    # project onto the xy plane
    #
    xp = quarkx.vect(x*mapx,x*mapy,0).normalized
    y = matrix_rot_z(math.pi/2)*xp
    return x, y, (x^y).normalized

def NewAxes(prevaxes, newx):
    try:
        mat=matrix_rot_u2v(prevaxes[0],newx)
        return newx, mat*prevaxes[1], mat*prevaxes[2]
    except:  # no angle
        return prevaxes

def MakeUniqueTargetname():
    import time
    return "t" + time.strftime("%Y%m%d%H%M%S", time.gmtime(time.time()))


SMALL=0.001

def getNormalFaces(faces, axis):
    def normalFace(face,axis=axis):
        return abs(face.normal*axis-1)<SMALL
    return filter(normalFace,faces)

def getends(group,x_axis):
    list = group.findallsubitems("",":f")
    return getNormalFaces(list,-x_axis), getNormalFaces(list,x_axis)

#
# Position following path points
#
class PositionFollowingDlg (quarkpy.dlgclasses.LiveEditDlg):
    #
    # dialog layout
    #

    endcolor = AQUA
    size = (210,250)
    dfsep = 0.50

    dlgdef = """
        {
        Style = "9"
        Caption = "Position Following"

        sep: = {Typ="S" Txt=" "}

        number: =
        {
        Txt = "Number"
        Typ = "EF1"
        Hint = "How many of the following path points to position" $0D " (new ones will be made if needed)"
        }

        sep: = {Typ="S" Txt=" "}

        angles: =
        {
        Txt = "First Pitch Yaw"
        Typ = "EQ"
        Hint = "Pitch Yaw angles to next, in degrees, map space"
        }

        sep: = {Typ="S" Txt=" "}

        more_angles: =
        {
        Txt = "More Pitch Yaw"
        Typ = "EQ"
        Hint = "Pitch Yaw angles for remaining, in degrees, relative to previous"
        }

        sep: = {Typ="S" Txt=" "}

        distance: =
         {
         Txt = "Distance"
         Typ = "EU"
         Hint = "Distance to next, in units"
         }


         sep: = {Typ="S" Txt=" "}

        shifttail: =
         {
         Txt = "Shift Tail"
         Typ = "X"
         Hint = "If checked, remaining points are moved to retain distance w.r.t last in moved series"
         }

         sep: = {Typ="S" Txt=" "}

         exit:py = { Txt=""}
    }
    """


class PathDuplicatorPointHandle(quarkpy.qhandles.IconHandle):

    def __init__(self, origin, centerof, pathdupmaster=0):
        quarkpy.qhandles.IconHandle.__init__(self, origin, centerof)
        self.pathdupmaster = pathdupmaster
        self.mainpathdup=self.centerof.parent.findname("Path Duplicator:d")


    def findpathdupcornerwith(self, list, entitykey, entitykeydata):
        for e in list:
            if e.type == ':d':
                if e[entitykey] == entitykeydata:
                    return e
        return None

    def makecopy(self, original):
        new = original.copy()
        # Erase all subitems from object.
        while (new.itemcount > 0):
            new.removeitem(new.itemcount-1)
        # Erase all key/keydata in object.
        for key in original.dictspec.keys():
            new[key] = ""
        # Copy only those key/keydata's we're interested in.
        new.shortname = "PathDup.Point"
        new["macro"] = "dup path_point"
        new["origin"] = original["origin"]
        new["target"] = original["target"]
        new["targetname"] = original["targetname"]
        return new

    #
    # For decent org., I wanted this to be a method of
    #  PathPointDuplicator, but couldn't get it to
    #  work.
    #
    def sourcelist2(self):
        myself = self.centerof
        list = []
        if (myself.parent is not None):
            for item in myself.parent.subitems:
                if item!=myself and (item.type!=':d' or quarkpy.mapduplicator.DupManager(item).siblingincluded(myself)):
                    list.append(item)
        return list

    def menu(self, editor, view):

        def after1click(m, self=self, editor=editor, view=view):
            # Insert a copy of me, between me and whoever I point to, and give this
            # new copy a unique targetname, which I should now point to instead.
            # Try also to put the new copy, into the tree-view in targeting-order.
            undo = quarkx.action()
            new = self.makecopy(self.centerof)
            new["targetname"] = MakeUniqueTargetname()
            undo.setspec(self.centerof, "target", new["targetname"])
            who_do_i_target = self.findpathdupcornerwith(self.centerof.parent.subitems, "targetname", new["target"])
            if (who_do_i_target is not None):
                newtranslate = (who_do_i_target.origin - self.centerof.origin) / 2
            else:
                prev_prev = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", self.centerof["targetname"])
                if (prev_prev is not None) and (prev_prev["macro"] == "dup path_point"):
                    newtranslate = (self.centerof.origin - prev_prev.origin)
                else:
                    newtranslate = quarkx.vect(64,0,0)
            new.translate(newtranslate)
            newtranslate = new.origin - quarkpy.qhandles.aligntogrid(new.origin, 1)
            new.translate(newtranslate)
            if ((who_do_i_target is not None) and (who_do_i_target.parent == self.centerof.parent)):
                undo.put(self.centerof.parent, new, who_do_i_target)
            else:
                undo.put(self.centerof.parent, new)
            editor.ok(undo, "Insert after PathDuplicator corner")
            editor.layout.explorer.sellist = [new]

        def before1click(m, self=self, editor=editor, view=view):
            # Insert a copy of me, between me and whoever points to ne, and give this
            # new copy a unique targetname, which my previous should point to instead.
            # Try also to put the new copy, into the tree-view in targeting-order.
            undo = quarkx.action()
            who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", self.centerof["targetname"])
            new = self.makecopy(self.centerof)
            new["targetname"] = MakeUniqueTargetname()
            new["target"] = self.centerof["targetname"]
            if (who_targets_me is not None) and (who_targets_me["macro"] == "dup path_point"):
                newtranslate = (who_targets_me.origin - self.centerof.origin) / 2
            else:
                next_next = self.findpathdupcornerwith(self.centerof.parent.subitems, "targetname", self.centerof["target"])
                if (next_next is not None) and (next_next["macro"] == "dup path_point"):
                    newtranslate = (self.centerof.origin - next_next.origin)
                else:
                    newtranslate = quarkx.vect(-64,0,0)
            new.translate(newtranslate)
            newtranslate = new.origin - quarkpy.qhandles.aligntogrid(new.origin, 1)
            new.translate(newtranslate)
            undo.put(self.centerof.parent, new, self.centerof)
            if (who_targets_me is not None):
                undo.setspec(who_targets_me, "target", new["targetname"])
            editor.ok(undo, "Insert before PathDuplicator corner")
            editor.layout.explorer.sellist = [new]

        def remove1click(m, self=self, editor=editor, view=view):
            # Tell whoever points to me, that it should now point to my target instead, as I am going to be removed now.
            undo = quarkx.action()
            what_is_my_target = self.centerof["target"]
            who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", self.centerof["targetname"])
            if who_targets_me is not None:
                undo.setspec(who_targets_me, "target", what_is_my_target)
            undo.exchange(self.centerof, None);
            editor.ok(undo, "Remove PathDuplicator corner");

        def speeddraw1click(m, self=self, editor=editor, view=view):
            #
            walker = self.centerof
            who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", walker["targetname"])
            while (who_targets_me is not None) and (who_targets_me["target"] is not None):
                walker = who_targets_me
                who_targets_me = self.findpathdupcornerwith(self.centerof.parent.subitems, "target", walker["targetname"])
            if (walker["speeddraw"] is not None):
                if (walker["speeddraw"] == "1"):
                    walker["speeddraw"] = "0"
                else:
                    walker["speeddraw"] = "1"
            #FIXME - How to redraw the duplicator, to reflect the change?!?


        def selectdup1click(m, self=self, editor=editor):
            editor.layout.explorer.uniquesel = self.mainpathdup
            editor.invalidateviews()

        def selecttail1click(m, self=self, editor=editor):
            center = self.centerof
            list = self.sourcelist2()
            pathlist = plugins.deckerutils.GetEntityChain(self.centerof["target"], list)
            editor.layout.explorer.sellist = [center] + pathlist
            editor.invalidateviews()

        def retarget1click(m, self=self, editor=editor):
            group = self.centerof.parent
            previous = self.centerof
            i=1
            undo = quarkx.action()
            for item in group.subitems:
                if item["macro"]=='dup path_point':
                    targetname = MakeUniqueTargetname()+'.'+`i`
                    i=i+1
                    undo.setspec(previous,"target",targetname)
                    undo.setspec(item,"targetname",targetname)
                    previous=item
            undo.setspec(previous,"target","dpathX")
            editor.ok(undo, 'retarget path corners')
            editor.layout.explorer.uniquesel=self.centerof
            editor.invalidateviews()

        def positionfollowing1click(m, self=self, editor=editor):
            class pack:
                  "stick stuff here"

            def setup(self,handle=self, pack=pack):
                list = handle.sourcelist2()
                if self.src["number"] is None:
                    self.src["number"] = 1,
                pathlist = plugins.deckerutils.GetEntityChain(handle.centerof["target"], list)
                thisorigin = handle.centerof.origin
                if pathlist:
                    nextorigin = pathlist[0].origin
                    dist = nextorigin-thisorigin
                    normdist = dist.normalized
                    self.src["distance"] = "%.2f"%abs(dist)
                    xax, yax, zax = (quarkx.vect(1,0,0),
                                    quarkx.vect(0,1,0),
                                    quarkx.vect(0,0,1))
                    pitch = "%.1f"%(math.asin(normdist*zax)/deg2rad)
                    yaw = "%.1f"%(math.atan2(normdist*yax, normdist*xax)/deg2rad)
                    self.src["angles"] = pitch+' '+yaw
                pack.list=pathlist
                pack.thisorigin=thisorigin

            def action(self, handle=self, editor=editor, pack=pack):
                if self.src["distance"]:
                    distance = eval(self.src["distance"])
                else:
                    distance = 0
                pitch, yaw = read2vec(self.src["angles"])
                if not distance or pitch is None:
                    return
                list = pack.list
                vangle = quarkx.vect(1,0,math.sin(pitch*deg2rad)).normalized
                normdist = matrix_rot_z(yaw*deg2rad)*vangle
                undo=quarkx.action()
                if list:
                    next = list[0]
                    new = next.copy()
                else:
                    new = handle.centerof.copy()
                    new["targetname"] = MakeUniqueTargetname()+'.0'
                    undo.setspec(handle.centerof,"target",new["targetname"])
                shift = pack.thisorigin+distance*normdist-new.origin
                new.translate(shift)
                if list:
                    undo.exchange(next, new)
                else:
                    undo.put(handle.centerof.parent,new)
                number, = self.src["number"]
                pitch2, yaw2 = read2vec(self.src["more_angles"])
                for i in range(1, number):
                    shift = None
                    if pitch2 is None:
                        continue
                    pitch = pitch+pitch2
                    yaw = yaw+yaw2
                    vangle=quarkx.vect(1,0,math.sin(pitch*deg2rad)).normalized
                    normdist = matrix_rot_z(yaw*deg2rad)*vangle
                    if i>=len(list):
                        new2 = new.copy()
                        #
                        # clock doesn't tick fast enough to give unique names
                        #
                        new2["targetname"] = MakeUniqueTargetname()+'.'+`i`
                        undo.setspec(new,"target",new2["targetname"])
                    else:
                        new2 = list[i].copy()
                    shift=new.origin+distance*normdist-new2.origin
                    new2.translate(shift)
                    if i>=len(list):
                        list.append(new2)
                        undo.put(new.parent, new2)
                    else:
                        undo.exchange(list[i],new2)
                    new = new2

                if self.src["shifttail"] and shift is not None:
                   for i in range(number,len(list)):
                       new = list[i].copy()
                       new.translate(shift)
                       undo.exchange(list[i], new)


                editor.ok(undo, "Move following path point(s)")
                editor.layout.explorer.uniquesel=handle.centerof

            PositionFollowingDlg(quarkx.clickform, 'positionfollowing', editor, setup, action)



        menulist = [qmenu.item("Insert after",  after1click)]
        if (self.pathdupmaster == 0):

            # if it is not the PathDup, then it must be a PathDupCorner, and several more menuitems are available
            menulist.append(qmenu.item("Insert before", before1click))
            menulist.append(qmenu.item("Remove",        remove1click))
            menulist.append(qmenu.item("Select main dup",   selectdup1click, "|Select main duplicator (making all path points visible)"))
            menulist.append(qmenu.item("Select tail", selecttail1click, "Multi-select this & the following path points"))
            menulist.append(qmenu.item("Position following", positionfollowing1click, "|Position following path points relative to this one, making new ones if necessary"))

        else:
            menulist.append(qmenu.item("Retarget Path", retarget1click, "Set target/targetname specifics, following subitem order"))
        menulist.append(qmenu.item("Toggle speeddraw",  speeddraw1click))

        return menulist


class PathPointHandle(PathDuplicatorPointHandle):

    def __init__(self, origin, centerof, mainpathdup):
        quarkpy.qhandles.IconHandle.__init__(self, origin, centerof)
        self.pathdupmaster = 0
        self.mainpathdup = mainpathdup


    #
    # called at end of drag, resets selection
    #
    def ok(self, editor, undo, old, new):
        PathDuplicatorPointHandle.ok(self,editor,undo,old,new)
        editor.layout.explorer.sellist=[self.mainpathdup.dup]

    def menu(self, editor, view):
        return PathDuplicatorPointHandle.menu(self, editor, view)

        def seldup1click(m,self=self,editor=editor):
            editor.layout.explorer.uniqusel=self.mainpathdup

        seldup = qmenu.item("Select duplicator",seldup1click,"select main duplicator (so that all path handles become visible)")
        pointhandles = pointhandles = [seldup]
        return pointhandles

class PathDuplicatorPoint(DuplicatorManager):

    Icon = (ico_dict['ico_mapdups'], 2)

    def buildimages(self, singleimage=None):
        pass

    def handles(self, editor, view):
        hndl = PathDuplicatorPointHandle(self.dup.origin, self.dup)
        return [hndl]

    def sourcelist2(self):
        myself = self.dup
        list = []
        if (myself.parent is not None):
            for item in myself.parent.subitems:
                if item!=myself and (item.type!=':d' or quarkpy.mapduplicator.DupManager(item).siblingincluded(myself)):
                    list.append(item)
        return list

class PathDuplicator(StandardDuplicator):

    cuberadius = 3096

    def readvalues(self):
        self.origin = self.dup.origin
        if self.origin is None:
            self.origin = quarkx.vect(0,0,0)
        self.matrix = None
        self.target = self.dup["target"]
        try:
           self.speed = int(self.dup["speeddraw"])
        except:
           self.speed = 0
        try:
           self.scaletex = int(self.dup["scaletexture"])
        except:
           self.scaletex = 0

    def applylinear(self, matrix, direct=0):
        pass

    def do(self, item):
        pass

    def sourcelist(self):
        list = StandardDuplicator.sourcelist(self)
        group = quarkx.newobj("group:g");
        for item in list:
           group.appenditem(item.copy())
        evaluateDuplicators(group)
        return group

    def sourcelist2(self):
        myself = self.dup
        list = []
        if (myself.parent is not None):
            for item in myself.parent.subitems:
                if item!=myself and (item.type!=':d' or quarkpy.mapduplicator.DupManager(item).siblingincluded(myself)):
                    list.append(item)
        return list

    def buildimages(self, singleimage=None):
        try:
            self.readvalues()
        except:
            print "Note: Invalid Duplicator Specific/Args."
            return

        pathlist = plugins.deckerutils.GetEntityChain(self.target, self.sourcelist2())
        #pathlist.insert(0, self.dup)

        templategroup = self.sourcelist()
        templatebbox = quarkx.boundingboxof([templategroup])
        templatesize = templatebbox[1] - templatebbox[0]

        # If SPEEDDRAW specific is set to one, we'll only use a single cube to make the path.
        if self.speed == 1:
           del templategroup
           templategroup = quarkx.newobj("group:g")
           templategroup.appenditem(plugins.deckerutils.NewXYZCube(templatesize.x, templatesize.y, templatesize.z, quarkx.setupsubset()["DefaultTexture"]))

        if (singleimage is None and self.speed != 1):
           viewabletemplategroup = templategroup.copy()
           viewabletemplategroup[";view"] = str(VF_IGNORETOBUILDMAP)  # Do not send this to .MAP file
           newobjs = [viewabletemplategroup]
        else:
           newobjs = []
        templatescale = min(templatesize.x, templatesize.y)/3
        OriginShift = -ObjectOrigin(templategroup)
        templategroup.translate(OriginShift, 0)    # Move it to (0,0,0)

        tile = templategroup.findname("Tile:g")
        if tile is not None:
            templategroup.removeitem(tile)

        templatefront=getNormalFaces(templategroup.findallsubitems("",":f"),
                                     quarkx.vect(1,0,0))
        rimvtxes=[]
        for face in templatefront:
            for vtxes in face.vertices:
                for vtx in vtxes:
                    rimvtxes.append(vtx)

#        # -- If SCALETEXTURES is on, use the linear() operation
#        if (self.scaletex != 0):
#           scalematrix = quarkx.matrix((2048/templatescale,0,0), (0,1,0), (0,0,1))
#        else:
#           # -- User does not want to scale textures (who wants), but then we must scale the polys in another way
#           flist = templategroup.findallsubitems("", ":f")
#           for f in flist:
#              f.translate(quarkx.vect(2048 - f.dist,0,0) * f.normal.x)

        count = len(pathlist)-1
        if (singleimage is not None) and (singleimage >= count): # Speed up Dissociate images processing
            return [] # Nothing more to send back!
        prevaxes = quarkx.vect(1,0,0),quarkx.vect(0,1,0),quarkx.vect(0,0,1)
        for i in range(count):
            #DECKER 2002-08-04 part-2: Moved this if-structure above the for-loop
            #if (singleimage is not None): # Speed up Dissociate images processing
            #   if (singleimage >= count):
            #      return [] # Nothing more to send back!
            #   #DECKER 2002-08-04: Removed this else, as it would cause dissociate images to produce an incorrect result.
            #   #                   Found by quantum_red in QuArK forum date: 2002-07-30 subject: "Texture Alignment Problem".
            #   #else:
            #   #   i = singleimage
            thisorigin = pathlist[i].origin
            nextorigin = pathlist[i+1].origin
            #print "Image#", i, "this", thisorigin, "next", nextorigin

            list = templategroup.copy()

            pathdist = nextorigin - thisorigin

#            # -- Place center between the two paths
#            neworigin = pathdist*0.5 + thisorigin

            #
            # place center so textures will tile from start
            #
            neworigin = pathdist.normalized*0.5*templatesize.z + thisorigin

            if (pathlist[i]["level"] or self.dup["level"]) and not Vertical(pathdist):
               prevaxes = MakeLevelAxes(pathdist.normalized)
            else:
               prevaxes = NewAxes(prevaxes,pathdist.normalized)
            xax, yax, zax = prevaxes
            #
            # N.B. when the three args are vectors they are indeed
            #  input as columns.  Tuples otoh will go in as rows.
            #
            mat = quarkx.matrix(xax,yax,zax)
            list.translate(neworigin, 0)
            list.linear(neworigin, mat)
            front, back = getends(list,xax)

            for face in front:
               center = projectpointtoplane(thisorigin,face.normal,face.dist*face.normal,face.normal)
               face.translate(thisorigin-center,0)
               if i>0:
                   if singleimage is not None:
                       lastx = (thisorigin-pathlist[i-1].origin).normalized
                       joinnorm=((xax+lastx)/2).normalized
                   face.distortion(-joinnorm,thisorigin)
            for face in back:
               center = projectpointtoplane(thisorigin,face.normal,face.dist*face.normal,face.normal)
               face.translate(nextorigin-center,0)
               if i<count-1:
                   nextx=(pathlist[i+2].origin-nextorigin).normalized
                   joinnorm=((xax+nextx)/2).normalized
                   face.distortion(joinnorm,nextorigin)

            #
            # find out where flat-ended segments would end if they
            #   are to just touch at the corners
            #
            def vtxshift(vtx,mat=mat,orig=thisorigin, shift=OriginShift):
                return mat*(vtx+shift)+orig
            if (tile is not None) or self.dup["squarend"]:
                startseg=endseg=0
                for vtx in map(vtxshift,rimvtxes):
                    frontpoint=projectpointtoplane(vtx,xax,thisorigin,front[0].normal)
                    frontproj = (frontpoint-thisorigin)*xax
                    startseg=max(startseg,frontproj)
                    backpoint=projectpointtoplane(vtx,-xax,nextorigin,back[0].normal)
                    backproj = -(backpoint-nextorigin)*xax
                    endseg=min(endseg,backproj)

            if tile is not None:
                tileableLength=abs(pathdist)-startseg+endseg
                tileOffset = (tileableLength%templatesize.x)/2
                tileTimes=int(tileableLength/templatesize.x)
                for i in range(tileTimes):
                    if i==0:
                        newTile=tile.copy()
                        newTile.linear(quarkx.vect(0,0,0),mat)
                        newTile.translate(thisorigin+(startseg+templatesize.x*0.5+tileOffset)*xax)
                    else:
                        newTile=newTile.copy()
                        newTile.translate(xax*templatesize.x)
                    list.appenditem(newTile)
            #
            #  Code below creates `box' shaped sections that just touch at the
            #   edges, or are set back.  Can't do it by taking vertices of actual
            #   front and back faces above because these don't seem to be
            #   computed yet.
            #
            if self.dup["squarend"]:
                 setback = self.dup["setback"]
                 if setback is None:
                     setback=0
                 else:
                     setback,=setback
                 if startseg:
                     start=xax*(startseg+setback)
                     for face in front:
                         face.translate(start)
                         face.distortion(-xax,thisorigin+start)
                 if endseg:
                     end=xax*(endseg-setback)
                     for face in back:
                         face.translate(end)
                         face.distortion(xax,nextorigin+end)

            if (singleimage is None) or (i==singleimage):
                newobjs = newobjs + [list]
            del list
            if (i==singleimage): # Speed up Dissociate images processing
                break
        return newobjs


    def handles(self, editor, view):
        try:
            self.readvalues()
        except:
            print "Note: Invalid Duplicator Specific/Args."
            return
        def makehandle(item,self=self):
            return PathPointHandle(item.origin, item, self)
        pathHandles=map(makehandle,plugins.deckerutils.GetEntityChain(self.target, self.sourcelist2()))

        return DuplicatorManager.handles(self, editor, view) + [PathDuplicatorPointHandle(self.dup.origin, self.dup, 1)]+pathHandles




class InstanceDuplicator(PathDuplicator):

    def handles(self, editor, view):
        h = StandardDuplicator.handles(self, editor, view)
        h[0].hint = "Copies of my contents are placed at the path points\n"
        if self.dup["usercenter"] is not None:
            h.append(UserCenterHandle(self.dup))
        return h


    def buildimages(self, singleimage=None):

        if len(self.dup.subitems)==0:
            return


        try:
            self.readvalues()
        except:
            print "Note: Invalid Duplicator Specific/Args."
            return

        pathlist = plugins.deckerutils.GetEntityChain(self.target, self.sourcelist2())
        #pathlist.insert(0, self.dup)


        templategroup = self.sourcelist()
        templatebbox = quarkx.boundingboxof([templategroup])
        templatesize = templatebbox[1] - templatebbox[0]

        newobjs = []
        if (singleimage is None and self.speed != 1):
           viewabletemplategroup = templategroup.copy()
           viewabletemplategroup[";view"] = str(VF_IGNORETOBUILDMAP)  # Do not send this to .MAP file
           newobjs = [viewabletemplategroup]
        else:
           newobjs = []
        templatescale = min(templatesize.x, templatesize.y)/3
#        templategroup.translate(-ObjectCustomOrigin(templategroup), 0)    # Move it to (0,0,0)
        if self.dup["usercenter"] is not None:
            templategroup.translate(-GetUserCenter(self.dup), 0)    # Move it to (0,0,0)
        elif self.dup["elbow"] is not None:
            templategroup.translate(-pathlist[1].origin, 0)
        else:
            templategroup.translate(-ObjectOrigin(templategroup), 0)    # Move it to (0,0,0)
        for item in templategroup.subitems[:]:
            if item.type==":d" and item["macro"]=="dup origin":
                templategroup.removeitem(item)

        count = len(pathlist)
        prevaxes = quarkx.vect(1,0,0),quarkx.vect(0,1,0),quarkx.vect(0,0,1)
        if self.dup["elbow"] == 1:
            retromat = ~(matrix_rot_u2v(prevaxes[0],(pathdist[1]-pathdist[0]).normalized))
            prevaxes = retromat*prevaxes[0],retromat*prevaxes[2],retromat*prevaxes[2],


#        debug('count '+`count`+' image '+`singleimage`)
        for i in range(count):

            if (singleimage is not None): # Speed up Dissociate images processing
               if (singleimage >= count):
                  return [] # Nothing more to send back!
               else:
                  i = singleimage

            thisorigin = pathlist[i].origin

            if (self.dup["track"] or self.dup["elbow"]) and count>1:
                if i<count-1:
                    nextorigin = pathlist[i+1].origin
                    pathdist = nextorigin-thisorigin
                    #
                    # otherwise just use last pathdist
                    #
#                if self.dup["elbow"] and i==count-1:
#                   pathdist=pathlist[count-2].origin-pathlist[count-1].origin
                if self.dup["elbow"]:
                   pathdist = thisorigin-pathlist[i-1].origin
                if (pathlist[i]["level"] or self.dup["level"]) and not Vertical(pathdist):
                   prevaxes = MakeLevelAxes(pathdist.normalized)
                else:
                   prevaxes = NewAxes(prevaxes,pathdist.normalized)
                xax, yax, zax = prevaxes
                #
                # N.B. when the three args are vectors they are indeed
                #  input as columns.  Tuples otoh will go in as rows.
                #
                matrix = quarkx.matrix(xax,yax,zax)
            else:
                matrix=quarkx.matrix('1 0 0 0 1 0 0 0 1')

#                debug("  image %d; i %d"%(singleimage, i))

            list = templategroup.subitems[0].copy()
            if not (pathlist[i]["no instance"] or (self.dup["elbow"] and (i==0 or i==count-1))):
                list.translate(thisorigin)
                list.linear(thisorigin,matrix)
                if (singleimage is None) or (i==singleimage):
                    newobjs = newobjs + [list]
            else:
                #
                # This cruft is necessary because dissociate1click
                #  stops the image-generation process when an empty
                #  image is returned
                #
                dummy = quarkx.newobj('dummy:g')
                newobjs = newobjs + [dummy]
            del list
            if (i==singleimage): # Speed up Dissociate images processing
                break

        return newobjs


def macro_instances(self):
    editor=mapeditor()
    if editor is None: return
    dup = editor.layout.explorer.uniquesel
    if dup is None: return
    undo = quarkx.action()
    new=dup.copy()
    new.shortname = 'Elbow duplicator'
    new["macro"] = 'dup instance'
    new["elbow"] = "1"
    for item in new.subitems[:]:
        new.removeitem(item)
    new.translate(quarkx.vect(64, -64, 0))
    undo.put(dup.parent, new, dup)
    editor.ok(undo,"add elbows")
    editor.layout.explorer.uniquesel=new

quarkpy.qmacro.MACRO_instances = macro_instances



quarkpy.mapduplicator.DupCodes.update({
  "dup path":       PathDuplicator,
  "dup path_point": PathDuplicatorPoint,
  "dup instance":   InstanceDuplicator,
})


# ----------- REVISION HISTORY ------------
#$Log: mapdupspath.py,v $
#Revision 1.47  2002/08/04 11:49:24  decker_dk
#Fixed some code that would cause dissociate images to produce an incorrect result.
#Found by quantum_red in QuArK-forum date: 2002-07-30 subject: "Texture Alignment Problem".
#
#Revision 1.46  2001/10/22 10:15:48  tiglari
#live pointer hunt, revise icon loading
#
#Revision 1.45  2001/07/08 20:57:56  tiglari
#change treatment of vertical 'level' segments
#
#Revision 1.44  2001/07/08 08:34:31  tiglari
#reinstated elbow button for path dup; added 'level' spec for path dup and
#  instance (elbow) dup, fixed orientation bugs
#
#Revision 1.43  2001/06/17 21:10:57  tiglari
#fix button captions
#
#Revision 1.42  2001/06/13 22:27:19  tiglari
#fix instance dup bug
#
#Revision 1.41  2001/06/09 01:21:17  tiglari
#fix due north bug (noted by greeze)
#
#Revision 1.40  2001/05/12 23:04:38  tiglari
#make new linear fixpoint behavior contingent on 'item center' flag
#
#Revision 1.39  2001/05/06 10:22:10  tiglari
#remove buildLinearMatrix stuff from instance duplicator buildimages
#
#Revision 1.38  2001/04/08 02:43:10  tiglari
#if a group inside the instance duplicator has a usercenter, then the
# matrix/scale/rotate attributes of a path point will apply the transformations
# to that group, around the center.  'track rotations' otoh apply w.r.t. the
# usercenter attribute of the instance duplicator itself, to all of the
# subitems of the instance duplicator collectively.
#
#Revision 1.37  2001/04/02 21:11:27  tiglari
#added is None to conditional
#
#Revision 1.36  2001/04/01 00:09:36  tiglari
#usercenter for matrices on path points for instance duplicator
#
#Revision 1.35  2001/03/31 10:18:16  tiglari
#revise instance duplicator to use usercenter specific
#
#Revision 1.34  2001/03/29 09:28:55  tiglari
#scale and rotate specifics for duplicators
#
#Revision 1.33  2001/03/28 23:21:03  tiglari
#re-integrate instance dup stuff from  1.31, which somehow got dropped
#
#Revision 1.32  2001/03/28 12:22:49  tiglari
#fix square end problem (vertex shift function wrong)
#
#Revision 1.25  2001/03/18 23:54:09  tiglari
#experimental merge
#
#
#Revision 1.24  2001/03/17 22:04:53  tiglari
#dissociate images fix
#
#Revision 1.23  2001/03/12 23:10:27  tiglari
#path dup adding/positioning enhancements (does the work of the
# 'torus generator' suggested plugin, inter alia)
#
#Revision 1.22.2.2  2001/03/12 09:21:23  tiglari
#retarget of path points (basically for if a duplicator is used to produce
#a complex pattern such as a spiral))
#
#Revision 1.22.2.1  2001/03/11 22:09:48  tiglari
#position/add path points with set angles
#
#Revision 1.22  2001/03/08 06:23:26  tiglari
#menu item to select duplicator on path point handles
#
#Revision 1.21  2001/03/07 20:00:13  tiglari
#specific for rotation suppression
#
#Revision 1.20  2001/03/04 06:40:13  tiglari
#changed to use arbitrary axis matrix rot from maputils
#
#Revision 1.19  2001/02/27 07:08:02  tiglari
#center tiles along path segments
#
#Revision 1.18  2001/02/27 05:33:07  tiglari
#fixed storage problem (map object created in class definition)
#
#Revision 1.17  2001/02/26 03:26:40  tiglari
#handle ok method fix
#
#Revision 1.16  2001/02/26 02:07:21  tiglari
#all path point handles appear when main dup is selected
#
#Revision 1.15  2001/02/25 16:32:54  decker_dk
#Fix for objects that are supposed to be checked for None/Nil/Null pointer.
#
#Revision 1.14  2001/02/23 03:41:51  tiglari
#square end and setback
#
#Revision 1.13  2001/02/22 21:47:57  tiglari
#stuff in a Tile subitem of the duplicator (top level) will now be tiled
#
#Revision 1.12  2001/02/22 03:37:37  tiglari
#more spiffup, also code for calculating start and end of maximal nonoverlapping
#flat-end path segments
#
#Revision 1.10  2001/02/21 06:34:22  tiglari
#a bit of cleanup, some preliminaries for elbows and tiling
#
#Revision 1.9  2001/02/20 21:31:38  tiglari
#textures tile from start of path (still messes at elbows, this probably
# needs fullon elbow segments to deal with)
#
#Revision 1.8  2001/02/19 19:15:57  decker_dk
#Insert before/after actions now places new 'handle' at more intutive position, and aligned-to-grid.
#
#Revision 1.7  2001/02/11 09:46:52  tiglari
#evaluation of duplicators within template
#
#Revision 1.6  2001/02/09 10:49:07  tiglari
#fixed bug with no-angle path joints
#
#Revision 1.5  2001/02/08 10:44:27  tiglari
#Fixed problems with paths going up & down.
# Replaced subtraction code with end-face movement/distortion.
#
#Revision 1.4  2001/02/04 11:51:45  decker_dk
#Some cleanup
#
#Revision 1.3  2001/02/03 19:08:30  decker_dk
#Changed path-handles to ':d'-macros, so functions can be performed on them. Like adding new path-handles.
#
#Revision 1.2  2001/01/27 18:25:29  decker_dk
#Renamed 'TextureDef' -> 'DefaultTexture'
#
#Revision 1.1  2000/12/19 21:07:19  decker_dk
#Still a buggy Path Duplicator
#
#2000-??-?? Using point-entities as path-handles
#1999-02-10 First public beta.
#1999-01-23 Created.
