"""   QuArK  -  Quake Army Knife

Vertex moveing plugin
"""

#
# THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
# FOUND IN FILE "COPYING.TXT"
#

# $Header: /cvsroot/quark/runtime/plugins/mapmovevertex.py,v 1.7 2002/05/18 22:38:31 tiglari Exp $

Info = {
   "plug-in":       "Vertex Movement Dialog",
   "desc":          "dialg for moving vertexes",
   "date":          "22 Sep 2000",
   "author":        "tiglari",
   "author e-mail": "tiglari@hexenworld.net",
   "quark":         "Version 6.x" }


#
# The import statements make material in other files available
#  to the statements in this file
#

#
# Quarkx is the delphi-defined API
#
import quarkx

#
# This imports the maphandles.py file in the quarkpy folder
#  To use something from this file you need to `quality' its
#  name with quarkpy.maphandles, e.g. `quarkpy.maphandles.VertexHandle`
# 
import quarkpy.maphandles

#
# for face menu options
#
import quarkpy.mapoptions

#
# This imports the tagging.py plugin in this folder
#
import tagging

#
# This imports every function in quarkpy\maputils
#  things imported in this way don't need to be (& in fact
#  can't be) qualified
#
from quarkpy.maputils import *

#
# For the stuff below about moving some object containing a vertex
#
from quarkpy import guiutils

#
# repeated from maputils to make it work with older quark versions.
#

def projectpointtoplane(p,n1,o2,n2):
  "project point to plane at o2 with normal n2 along normal n1"
  v1 = o2-p
  v2 = v1*n2
  v3 = n1*n2
  v4 = v2/v3
  v5 = v4*n1
  return p + v5

#
# A utility for accessing attributes of objects
#  (class instances) that might not be defined
#
def getAttr(object, attr, default=None):
    if hasattr(object, attr):
        return getattr(object,attr)
    else:
        return default
#
# ditto for deleting attributes
#
def delAttr(object, attr):
    if hasattr(object, attr):
         delattr(object, attr)

def appendToAttr(object, attr, thing):
    if hasattr(object, attr):
        getattr(object,attr).append(thing)
    else:
        setattr(object,attr,[thing])

def removeFromAttr(object, attr, thing):
    getattr(object,attr).remove(thing)
    if getattr(object,attr)==[]:
        delattr(object,attr)
#
# This is a definition of the vertex-movement dialog
# The `live edit dialog' is a rather fancy critter defined
#  in quarkpy/dlgclasses.  Don't worry about its innards,
#  but you'll see how it's used further below.
#
# Read http://..adv.quarkpy.gui.html#dialogs in the infobase
#  for details on how this stuff defines the appearance of
#  dialog and the data-entry forms in it.
#
#  `EU` is the form with a field that you can enter a numerical
#   value, and up-down buttons for increment/decrement
#
#  Somebody has really gotta gettaroundto documenting all the
#   different Typ's...
#

class VtxDragDlg(quarkpy.dlgclasses.LiveEditDlg):
    #
    # dialog layout
    #

    endcolor = AQUA
    size = (140,120)
    dfsep = 0.50

    dlgdef = """
        {
            Style = "9"
        Caption = "Vertex Movement"

        X: = 
        {
        Txt = "X"
        Typ = "EU"
        Hint = "X position"
        }

        Y: = 
        {
        Txt = "Y"
        Typ = "EU"
        Hint = "Y position"
        }

        Z: = 
        {
        Txt = "Z"
        Typ = "EU"
        Hint = "Z position"
        }

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

        gridpos: =
        {
        Txt = "Grid Position"
        Typ="X"
        Hint="If this is checked, vertexes will move to grid positions"
        }
        
        griddel: =
        {
        Txt = "Grid Delta"
        Typ="X"
        Hint="If this is checked, vertexes will move by grid increments" $0D " (overridden by Grid Position)"
        }

        sloppy: =
        {
        Txt = "Sloppy"
        Typ="X"
        Hint="If this is checked, vertexes move in the plane of a locked face, but not necessarily to where you specify"
        }

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

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

#
# Main math function.  Might shift into maputils.
#
def rotateFace(face, mvtx, delta, pivot, pivot2):
    "rotates face from mvtx by delta around pivots"
#    debug('delta: '+`delta`)
    #
    # Much more laborious than Tim Smith's maphandles
    #  method, but seems to prevent `drifting' of supposedly
    #  locked vertices from their locked positions
    #
    # The idea if first to use the pivots and
    #  the new position as threepoints, then fix up
    #  the texture by rotating the texture threepoints,
    #  (code 3), and projecting them onto the new face.
    #
    # Probably a more streamlined approach will work, but
    #  the drifting problem is a pain.
    #
    rotationAxis = (pivot2-pivot).normalized
    norm1 = (rotationAxis^(mvtx-pivot)).normalized
    plane1 = rotationAxis^norm1
    norm2 = (rotationAxis^(mvtx+delta-pivot)).normalized
    plane2 = rotationAxis^norm2
    points = face.threepoints(3)
    facenorm = face.normal
    def proj(v, org=pivot, ax1=rotationAxis, ax2=plane1):
        return (v-org)*ax1, (v-org)*ax2
    #
    # map applies proj to each member in the tuple `points',
    #  producing the new tuple `pcoords'
    #
    pcoords = map(proj, points)
    face.setthreepoints((pivot2, pivot, mvtx+delta),0)
    if facenorm*face.normal<0:
        face.swapsides()
    newpoints = face.threepoints(0)
    def proj2plane(p,pp=newpoints[1],nn=face.normal):
        return projectpointtoplane(p,nn,pp,nn)
    def unproj((x, y),org=pivot,ax1=rotationAxis, ax2=plane2):
        return org+x*ax1+y*ax2
    #
    # Well I could have piled up three maps, and not
    #  used the pcoords variable, but I didn't!
    #
    newpoints = tuple(map(proj2plane, (map(unproj, pcoords))))
    face.setthreepoints(newpoints,3)
    

#
# Here's the function that does the real work of moving the faces.
# It's a good idea to separate out the functions & classes that
#  perform significant work on the data being manipulated from
#  those that are basically oriented towards the UI
#
def moveFaces(faces, mvtx, delta, poly, locklist, freezelist, sloppy=None):
    "returns the moving old, new faces, of poly , with mvtx moved by delta"
    result = []
    old = []
    for face in faces:
        if face in freezelist:
            continue
        newface = face.copy()
        norm = face.normal
        facelocked = []
        #
        # regular index method doesn't work for lists of vectors
        #
        vtxes = face.verticesof(poly)
        vtxindex = 0
        for vtx in vtxes:
            if not(vtx-mvtx):
                moveindex = vtxindex   
            for vtxh in locklist:
                #
                # Testing for closeness rather than full-precision
                #  superposition seems to be necessary to keep the
                #  locked vertices from coming adrift.  There's
                #  no theory behind the choice of value; it just
                #  seems to work.
                #
                if (vtxh.poly is poly and abs(vtxh.pos-vtx)<.1):
                    facelocked.append(vtxh.pos)
                    #
                    # we only use this if only one vertex is locked
                    #
                    lockindex=vtxindex
                vtxindex=vtxindex+1
        if len(facelocked) == 0:
            proj = delta*norm
            newface.translate(proj*norm)
#            debug('translated '+newface.shortname)    
        if len(facelocked) == 1:
            #
            # this autolock stuff isn't working right so its
            #  dialog checkbox is commented out to disableit
            #
            if 0:
#            if autolock:
                cyclelength = len(vtxes)
                if cyclelength/abs(moveindex-lockindex)!=2:
                    #
                    # I think a mathematician could do this better
                    #
                    halfcycle = cyclelength/2.0
                    if moveindex<lockindex:
                        if lockindex-moveindex<halfcycle:
                            secondlockindex=lockindex+1
                        else:
                            secondlockindex=lockindex-1
                    elif lockindex<moveindex:
                        if moveindex-lockindex<halfcycle:
                            secondlockindex=lockindex-1
                        else:
                            secondlockindex=lockindex+1
                    #
                    # the remainder operation is the go for cyclic
                    #  indexing situations.
                    #
                    facelocked.append(vtxes[secondlockindex%cyclelength])
        if len(facelocked)==1:        
                #
                # rotate around perp to line from mftx to pivot
                #
                pivot=facelocked[0]
                perp = newface.normal^(mvtx-pivot)
                rotateFace(newface, mvtx, delta, pivot, pivot+perp)
        if len(facelocked) == 2:
            rotateFace(newface, mvtx, delta, facelocked[0], facelocked[1])
#            debug('2-rotated '+newface.shortname)
        if len(facelocked) > 2:
            if sloppy:
                continue
            norm = face.normal
            #
            # Bail unless the new vertex position is on the
            #  fully locked plane.
            #
            if norm*(mvtx+delta-facelocked[0]):
                return None, None
        old.append(face)
        result.append(newface)
    return old, result

#
# Now we get going on the interface.
#

#
# First define a new menu (see the plugins tutorial for the
#  general method)
#
def vertexmenu(self, editor, view, oldmenu=quarkpy.maphandles.VertexHandle.menu.im_func):

    #
    # This is a very convenient approach to defining the `callback'
    #  functions that are called when a menu-item is clicked; the
    #  default-argument specifications make it easy to pass all
    #  sorts of info to the functions (e.g. self in the function
    #  is equated to self in the environment).
    # Won't work for callbacks that want to also be called from
    #  the command menu or a toolbar button (like some of the ones
    #  in maptagside)
    #
    def moveclick(n, self=self, editor=editor):
         pos = self.pos
         poly = self.poly
         faces = poly.faces
         #
         # find out what faces are going to move
         #
         moving = []
         for face in faces:
             for vtx in face.verticesof(poly):
                 #
                 # Remember = doesn't work right for vectors;
                 #  this is how you test for two vectors being
                 #  equal
                 #
                 if not (vtx-pos):
                    moving.append(face)
                    continue
    
         #
         # Now start setting up the live edit dialog; this is
         #  a black magic process that you just follow
         #
         
         #
         #  The objects that the dialog is going to manipulate
         #   are stuffed into a `pack' object
         #
         class pack:
             "a place to stick stuff"
         pack.poly = poly
         pack.moving = moving
         pack.pos = pos
 
         #
         # Now define a setup function with `self' as its first
         #  argument, and any number of further default arguments
         #  specified to in stuff from above. 
         #
         # `self' refers the dialog, it's .src is the object
         #  from which the displayed values in the dialog are
         #  taken
         #
         def setup(self, pack=pack, editor=editor):
             src = self.src
             options = quarkx.setupsubset(SS_MAP, "Options")
             src["gridpos"]=options["movevertex_gridpos"]
             src["griddel"]=options["movevertex_griddel"]
             src["sloppy"]=options["movevertex_sloppy"]
             pos = pack.pos
             #
             # Tell the editor the position of the moving vertex,
             #   so that a green box can be drawn around it
             #
             editor.movingvertex = pos
             editor.invalidateviews()
             #
             # this code loads the x, y and z coordinates,
             #  represented as strings, into the X, Y and Z
             #  specifics of src, and hence ultimately into
             #  the corresponding controls of the dialog
             #
             self.src["X"] = "%f"%pos.x
             self.src["Y"] = "%f"%pos.y
             self.src["Z"] = "%f"%pos.z
             
         #
         # `action' is the function that is called when the
         #   data in the dialog box is changed
         #
         def action(self, pack=pack, editor=editor):
             #
             # read the new data from self.src
             #
             src = self.src
             #
             # Showing off with lambda as well as map. Could be
             # done three statements, in the style of setup above
             #
             x,y,z = map(lambda d,src=src:eval(src[d]),("X","Y","Z"))
             newpos = quarkx.vect(x,y,z)
             #
             # calculate the desired movement, get list of locked
             #  vertices, etc.
             #
             delta = newpos-pack.pos
             #
             # grid stuff
             #
             gridstep = editor.gridstep
             #
             # There's tricky approximation stuff here, just empirical,
             #  I don't understand the math.
             #
             if gridstep:
                 if src["gridpos"]:
                     #
                     # map a delta component
                     #
                     def delcom(d, p, gridstep=gridstep):
                         offgrid = p%gridstep
                         #
                         # or should we force to grid?
                         #
                         if abs(d)<0.01:
                            return 0.0
                         #
                         # little change, increment/decrement
                         #
                         if abs(d)<=1.0:
                             if d>0:
                                 return gridstep-offgrid
                             else:
                                 return -gridstep-offgrid
                         #
                         # otherwise round off change so final
                         #  pozzie goes to nearest gridpoint
                         #
                         offgrid = (p+d)%gridstep
                         if offgrid<(gridstep/2.0):
                             return d-offgrid
                         else:
                             return d+gridstep-offgrid
                 elif src["griddel"]:
#                     debug('griddel')
                     def delcom(d, p, gridstep=gridstep):
                         if abs(d)<.01:
                             return 0.0
                         if abs(d)<=1.0:
                             if d>0:
                                 return gridstep
                             else:
                                 return -gridstep
                         offgrid = d%gridstep
                         if offgrid<(gridstep/2.0):
                             return d-offgrid
                         else:
                             return d+gridstep-offgrid
                 else:
                     def delcom(d, p, gridstep=gridstep):
                         return d
#                 debug('orig delta: '+`delta`)
                 delta = quarkx.vect(tuple(map(delcom,delta.tuple, pack.pos.tuple)))
             oldfaces=pack.moving
             locklist = getAttr(editor,'lockedVertices',[])
             freezelist = getAttr(editor,'frozenFaces',[])
             #
             # calculate the new faces
             #
             oldfaces, newfaces = moveFaces(oldfaces, pack.pos, delta, pack.poly, locklist, freezelist, src["sloppy"])
             #
             # Mere return from function produces None return value.
             #  exceptions would be the way to go if there were
             #  more different kinds of failure to deal with.
             #
             # A `computational' routine such as moveFaces shouldn't
             #  throw up error dialog boxes itself, but leave this
             #  to the callers.
             #
             if newfaces is None:
                 quarkx.msgbox("Face can't move because of locks",
                     MT_ERROR, MB_OK)
                 return
             #
             # swap them into the map
             #
             undo=quarkx.action()
             for i in range(len(oldfaces)):
                undo.exchange(oldfaces[i], newfaces[i])
             editor.ok(undo, "Move vertex")
             #
             # check for lost vertex
             #
             newpos = pack.pos+delta
             if not freezelist:
               for face in newfaces:
                 for vtx in face.verticesof(pack.poly):
                     if abs(newpos-vtx)<.01:
                         break
                 else:
                     #
                     # For some unknown magical reason, the appearance
                     #  of this dialog causes the mov
                     #
                     if not src["sloppy"]:
                         quarkx.msgbox("Moving Vertex Lost",MT_INFORMATION,MB_OK)
                 break
                 
             #
             # Now put the replacement info into pack (if
             #  this isn't done, undo won't work right for
             #  repeat entry of data info dialog
             #
             pack.moving = newfaces
             pack.pos = newpos
             #
             # For some unkoown reason, the exchange operation
             #  above causes the new stuff to become the selection;
             #  so we set things back the way they were
             #
             editor.layout.explorer.sellist=[pack.poly]
             #
             # and reset the options in case we've changed them
             #
             options = quarkx.setupsubset(SS_MAP, "Options")
             options["movevertex_gridpos"]=self.src["gridpos"]
             options["movevertex_griddel"]=self.src["griddel"]
             options["movevertex_sloppy"]=self.src["sloppy"]
             
         #
         # This is an optional callback for final cleanup
         #
         def onclosing(self, editor=editor):
             delAttr(editor,'movingvertex')
             editor.invalidateviews()
         
         #
         # And at least create the dialog.
         # quarkx.clickform is the window last clicked in,
         #  needed here by the rules of Black Magic
         # 'vtxdrag' is the tag under which the dimensions
         #  of the box are retained in setup.qrk (value of
         #  dlgdim_vtxdrag specific); to check the size that
         #  the dialog definition above is producing (size),
         #  delete this from setup.qrk after closing quark,
         #  and then reopen & fire up the dialog.
         # Then editor is required, and the first two `callback'
         #  functions we've defined, and the last as an option.
         #
         VtxDragDlg(quarkx.clickform, 'vtxdrag', editor, setup, action, onclosing)
             
    #
    # And now a bunch of simple litte menu item callbacks
    #
    def lockclick(m, self=self, editor=editor):
        try:
            editor.lockedVertices.append(self)
        except (AttributeError):
            editor.lockedVertices = [self]
        #
        # redraw the views to see the effect.
        #
        editor.invalidateviews()
        
    def clearclick(m, self=self, editor=editor):
        delAttr(editor,'lockedVertices')
        delAttr(editor,'frozenFaces')
        editor.invalidateviews()
    
    def unlockclick(m, editor=editor):
        removeFromAttr(editor,'lockedVertices',m.unlockMe)
#        editor.lockedVertices.remove(m.unlockMe)
#        if editor.lockedVertices == []:
#            del editor.lockedVertices
        editor.invalidateviews()
 

    #
    # Moving a containing group so that the vertex shifts to ongrid
    #
    def moveparentpopup(self=self,editor=editor):
        shift=self.pos-editor.aligntogrid(self.pos)

        def makeitem(object,editor,r,shift=shift):

            def Shifter(m, object=object, editor=editor, shift=shift):
                new=object.copy()
                undo=quarkx.action()
                new.translate(shift)
                undo.exchange(object, new)
                editor.ok(undo, "Move object")
                editor.invalidateviews()

            item = qmenu.item(object.name,Shifter, "Move me")
            item.menuicon = object.geticon(1)
            return item
            
        poly=self.poly
        list=guiutils.buildParentPopupList(self.poly,makeitem, editor)
        return list
     
    #
    # And their menu items
    #
    moveparentitem = qmenu.popup("&Move Containing", moveparentpopup(),
      hint="|The clicked-on containing object of this vertex will be moved so as to force the vertex onto the grid.")
    moveitem = qmenu.item("&Move Vertex", moveclick)
    lockitem = qmenu.item("&Lock Vertex", lockclick)
    unlockitem = qmenu.item("&Unlock Vertex", unlockclick)
    clearitem = qmenu.item("&Clear Locks", clearclick)
    #
    # And a bit of convoluted logic for enable/disable
    #
    locker = lockitem
    try:
        locklist = editor.lockedVertices
    except (AttributeError):
        pass
    else:
        for vtxh in locklist:
            if not(vtxh.pos-self.pos):
                unlockitem.unlockMe = vtxh
                locker = unlockitem
                moveitem.state = qmenu.disabled
                break
    #
    # And we return the menu, the new items stuck onto the
    #  front of the old.
    #
    return [moveparentitem, moveitem, locker, clearitem]+oldmenu(self,editor,view)
    
#
# And here's the magic bit; when vertexmenu got defined, the
#  original menu function for quarkpy.maphandles.VertexHandle
#  got stored within it (as `oldmenu'); now we replace
#  it in the quarkpy.maphandles.VertexHandle with out new
#  creation
#
quarkpy.maphandles.VertexHandle.menu = vertexmenu

#
# Should be generalized from tagging
#

#
# And a different version of the same trick to get the little
#  blue squares drawn around the locked vertices, this time by
#  replacing quarkpy.qbaseeditor.BaseEditor.finishdrawing
#
# Also the green square around the moving vertex
#
from tagging import drawredface # misnamed, oh well

def lockfinishdrawing(editor, view, oldmore=quarkpy.qbaseeditor.BaseEditor.finishdrawing):
      cv = view.canvas()
      try:
         locks = editor.lockedVertices
      except (AttributeError):
         pass
      else:
         cv.pencolor = MapColor("Duplicator")
         for vtxh in locks: # these are handles
             p1 = view.proj(vtxh.pos)
             tagging.drawsquare(cv,p1,8)
      try:
         moving = editor.movingvertex
         p1 = view.proj(moving)
      except (AttributeError):
         pass
      else:
         if view.info["type"] == "3D":
              scalefactor = 50
         else:
             scalefactor = 30
         scale = view.scale(moving)
         for (color, axis) in (MapColor("Tag"), (1,0,0)), (MapColor("Bezier"), (0,1,0)),(MapColor("Duplicator"), (0,0,1)):
             cv.pencolor = color
             p0 = view.proj(moving)
             p1 = view.proj(moving+(scalefactor/scale)*quarkx.vect(axis))
             cv.line(p0, p1)
                 
#         else:
#             cv.pencolor = MapColor("Bezier")
#             tagging.drawsquare(cv,p1,8)
              

      cv.pencolor=MapColor("Duplicator")
      try:
          for face in editor.frozenFaces:
              drawredface(view,cv,face)
      except (AttributeError):
          pass
      
      oldmore(editor, view)

quarkpy.qbaseeditor.BaseEditor.finishdrawing = lockfinishdrawing


def vec2rads(v):
    "returns pitch, yaw, in radians"
    v = v.normalized
    import math
    pitch = -math.sin(v.z)
    yaw = math.atan2(v.y, v.x)
    return pitch, yaw

def find3DView(editor):
    views = []
    for v in editor.layout.views:
        if v.info["type"]=="3D":
            views.append(v)
    if views == []:
        return
    return views[0]


#
# And now the whole bizness again to lock every vertex of a face
#
def lockfacemenu(o, editor, oldfacemenu = quarkpy.mapentities.FaceType.menu.im_func):
    
    def lockclick(m,face=o,editor=editor):
        locks = getAttr(editor,'lockedVertices',[])
        for poly in face.faceof:
            for vtx in face.verticesof(poly):
                locks.append(quarkpy.maphandles.VertexHandle(vtx, poly))
        editor.lockedVertices = locks
        editor.invalidateviews()
     
    def unlockclick(m, face=o, editor=editor):
        try:
            locks = editor.lockedVertices
        except (AttributeError):
            return
        for poly in face.faceof:
            for vtx in face.verticesof(poly):
                for hvtx in locks[:]:
                    if not (hvtx.pos-vtx):
                        locks.remove(hvtx)
        editor.invalidateviews()
                        
    def viewclick(m, face=o, editor=editor):
        if quarkx.keydown('\020')==1: # shift is down
            reverse = 1
        else:
            reverse = 0
        clickview = quarkx.clickform.focus 
        #
        # clickform doesn't seem to work for floating 3d windows
        #  so we just take the first.
        #
        if clickview is not None and clickview.info["type"]=="3D":
            view = clickview
        else:
            view = find3DView(editor)
            if view is None:
                quarkx.msgbox("Need an open 3D view for this one!",
                   MT_ERROR,MB_OK)
                return
        #
        # Should have a 3D view here
        #
        pos, yaw, pitch = view.cameraposition
        dist = abs(pos - face.origin)
        if reverse:
            norm = -face.normal
        else:
            norm = face.normal
        newpos = face.origin+dist*(norm)
        pitch, yaw = vec2rads(-norm)
        view.cameraposition = newpos, yaw, pitch
        editor.invalidateviews()

    def freezeclick(m, face=o, editor=editor):
        appendToAttr(editor, 'frozenFaces', face)
        editor.invalidateviews()
        
    def unfreezeclick(m, face=o, editor=editor):
        removeFromAttr(editor, 'frozenFaces', face)
        editor.invalidateviews()

    lockitem = qmenu.item("Lock Vertices",lockclick,"Locks all vertices on this face")
    unlockitem = qmenu.item("Unlock Vertices", unlockclick,"Unlocks vertices on this face")
    freezeitem = qmenu.item("Freeze Face", freezeclick,"|Plane of face doesn't move, so vertices are confined to move within it. (So they don't go exactly where they're told to).")
    unfreezeitem = qmenu.item("Unfreeze Face", unfreezeclick, "|To unfreeze all frozen faces, clear locks on vertex menu.")
    freezer = freezeitem
    try:
        freezelist = editor.frozenFaces
    except (AttributeError):
        pass
    else:
        for face in freezelist:
            if o is face:
                freezer = unfreezeitem
                break
                
    viewitem = qmenu.item("Look At", viewclick, "|An open 3D view shifts to look at this face head on.\n (SHIFT to look at the face from the back)")

    locking = qmenu.popup("Lock/Freeze",[lockitem,unlockitem, freezer],
        hint = "|Submenu for items that are supposed to help in placing vertices by controlling which faces & vertices can move and which can't.")

    return [locking, viewitem]+oldfacemenu(o, editor)

quarkpy.mapentities.FaceType.menu = lockfacemenu


#
#  Code for looking at center of selection
#
def originmenu(self, editor, view, oldoriginmenu = quarkpy.qhandles.GenericHandle.OriginItems.im_func):
  
    menu = oldoriginmenu(self, editor, view)
    
    if view is not None:

      def seePointClick(m,self=self,editor=editor):
        view = find3DView(editor)
        pos, yaw, pitch = view.cameraposition
        dir = (self.pos-pos).normalized
        pitch, yaw = vec2rads(dir)
        view.cameraposition = pos, yaw, pitch
        editor.invalidateviews()

      seeitem = qmenu.item("Look To",seePointClick,"|Aims an open 3d view at object")
    
      menu[1:1] = [seeitem]

    return menu

quarkpy.qhandles.GenericHandle.OriginItems = originmenu


#
# Proper version of this now committed to main branch
#
#
# 3d view camera circlestrafe
#   This stuff is to be supported in qhandles as "C" mouse-drag
#   option (Shift-LMB drag by default), so this code will
#   be removed from the final version.
#

def circleinit(self, editor, view, x, y, old = quarkpy.qhandles.SideStepDragObject.__init__):
    self.editor = editor
    old(self, editor, view, x, y)
    
quarkpy.qhandles.SideStepDragObject.__init__= circleinit
    
def circledragto(self, x, y, flags, olddragto=quarkpy.qhandles.SideStepDragObject.dragto):
    sel = self.editor.layout.explorer.sellist
    if sel and quarkx.keydown('Z')==1:
        min, max = quarkx.boundingboxof(sel)
        center = .5*(max+min)
        pos, yaw, pitch = self.camerapos0
        dist = abs(pos-center)
        x = self.x0-x
        y = self.y0-y
        newdir = (pos + self.vleft*x + self.vtop*y - center).normalized
        newpos = center+dist*newdir
        pitch, yaw = vec2rads(-newdir)
        self.view.animation = 1
        self.view.cameraposition = newpos, yaw, pitch
    else:
        olddragto(self, x, y, flags)

quarkpy.qhandles.SideStepDragObject.dragto = circledragto

# $Log: mapmovevertex.py,v $
# Revision 1.7  2002/05/18 22:38:31  tiglari
# remove debug statement
#
# Revision 1.6  2002/04/01 08:30:05  tiglari
# shift out the parentpopup menu stuff (to quarkpy.guiutils)
#
# Revision 1.5  2002/03/30 09:31:14  tiglari
# Add 'move containing' item to the vertex RMB that moves a containing
#   object so that the vertex becomes on-grid.
#
# Revision 1.4  2001/06/17 21:10:57  tiglari
# fix button captions
#
# Revision 1.3  2001/06/16 03:19:05  tiglari
# add Txt="" to separators that need it
#
# Revision 1.2  2001/05/24 22:28:05  tiglari
# fix tuple bug, improved anti-drift
#
# Revision 1.1  2001/04/01 22:45:31  tiglari
# initial commit
#