
########################################################
#
#               Side Tagging and Glueing Plugin
#                      v1.1, Nov 3, 1998
#                      works with Quark5c5            
#
#
#        by tiglari@hexenworld.com, with lots of advice
#         and code snippets from Armin Rigo
#     
#   Possible extensions:
#    - button & floating toolbar as well as menu commands
#    - extension to vertex tagging & glueing
#    - tracking of tagged sides that move
#   I'm not sure how useful any of these would really be, and
#   think there's other stuff that's a higher priority now.
#
#   You may freely distribute modified & extended versions of
#   this plugin as long as you give due credit to tiglari &
#   Armin Rigo.
#
#   Please notify bugs & improvements to tiglari@hexenworld.com
#
##########################################################

Info = {
   "plug-in":       "Side Tag & Glue",
   "desc":          "Side tagging and gluing to tagged side",
   "date":          "3 nov 98",
   "author":        "tiglari",
   "author e-mail": "tiglari@hexenworld.com",
   "quark":         "Version 5.0.c5" }

import quarkx
import quarkpy.mapmenus
import quarkpy.mapentities
import quarkpy.qmenu
import quarkpy.mapeditor
import quarkpy.mapcommands
from quarkpy.maputils import *


#
# utilities
#
def gettagged(o):
  " safe fetch of tagging.tagged attribute"
  try:
    return o.tagging.tagged
  except (AttributeError): return None

#
#  Now the right-mouse button menu for sides.
#

#
#  stash the old one
#
oldfacemenu = quarkpy.mapentities.FaceType.menu.im_func
	
def gluemenuitem(String, ClickFunction,o):
  "make a menu-item with a side attached"
  item = qmenu.item(String, ClickFunction)
  item.side = o
  return item

def tagmenu(o, editor):
  "the new right-mouse for sides"
  menu = oldfacemenu(o, editor)
  tagged = gettagged(editor)
  menu[:0] = [gluemenuitem("Tag side",TagSideClick,o)]
  if not tagged is None:
    menu[:0] = [qmenu.item("Clear Tag",ClearTagClick)]
    menu[:0] = [gluemenuitem("Glue to tagged", GlueSideClick,o)]
  return menu  

#
# now bung in the new one.
#
quarkpy.mapentities.FaceType.menu = tagmenu


#
#  Ditto for the right-mouse-button menu for vertices
#

oldvertmenu = quarkpy.maphandles.VertexHandle.menu.im_func


def tagvertmenu(self, editor, view):
  menu = oldvertmenu(self,editor,view)
  tagged = gettagged(editor)
  if not tagged is None:
    selection = editor.layout.explorer.sellist
    if isoneface(selection) and not selection[0] is tagged:
      item = gluemenuitem("Align selection to tagged", GlueSideClick, selection[0])
      item.fulcrum = self.pos
      menu[:0]=[item]
  return menu

quarkpy.maphandles.VertexHandle.menu = tagvertmenu

#
#  Now for the actual side-tagging machinery
#

class Tagging:
  "a place to stick side-tagging stuff"
  tagged = None
  oldfinishdrawing = None  # where we will stash the original


def drawsquare(cv, o, side):
  "function to draw a square around o"
  dl = side/2
  cv.brushstyle = BS_CLEAR
  cv.rectangle(o.x+dl, o.y+dl, o.x-dl, o.y-dl)


def checktree(root, obj):
  while obj is not root:
    t = obj.parent
    if t is None or not (obj in t.subitems):
      return 0
    obj = t
  return 1

    
def tagfinishdrawing(self, view): 
  "the new finishdrawning routine"
  Tagging.oldfinishdrawing(self, view)
  editor = mapeditor()
  tagged = gettagged(editor)
  if tagged is None: return
  #
  # clear tag if face no longer in map
  #   
  if not checktree(editor.Root,tagged):
    ClearTagClick(None)
    return
  cv = view.canvas()
  cv.pencolor = MapColor("Tag")
  for vtx in editor.tagging.tagged.vertices: # is a list of lists
    sum = quarkx.vect(0, 0, 0)
    p2 = view.proj(vtx[-1])  # the last one
    for v in vtx:
      p1 = p2
      p2 = view.proj(v)
      sum = sum + p2
      cv.line(p1,p2)
    drawsquare(cv, sum/len(vtx), 8)

#
#  Menu item commands
#

def isoneface(selections, msgboxes=0):
    if selections is None: return 0
    if msgboxes == 0:
      if len(selections) == 1 and selections[0].type == ":f":
        return 1
      else: return 0
    elif (len(selections) < 1):
      quarkx.msgbox("No selection", MT_ERROR, MB_OK)
    elif (len(selections) > 1):
      quarkx.msgbox("Only one selection allowed", MT_ERROR, MB_OK) 
    elif (selections[0].type!= ":f"):
      quarkx.msgbox("The selected object is not a face", MT_ERROR, MB_OK) 
    else:
      return 1
    return 0


def TagSideClick (m):
  "tags a side on mouse-click, also replaces finishdrawing"
  editor = mapeditor()
  if editor is None: return
  editor.tagging = Tagging()
  try:
    editor.tagging.tagged = m.side
  except (AttributeError) :
    tagged = editor.layout.explorer.sellist
    if not isoneface(tagged, 1):
      return
    editor.visualselection()       #clears handle on tagged side(s)
    editor.tagging.tagged = tagged[0]
  global mencleartag
  mencleartag.state = qmenu.normal
  global menglueside
  menglueside.state = qmenu.normal
  if Tagging.oldfinishdrawing is None:  # we haven't done this yet
    Tagging.oldfinishdrawing = quarkpy.mapeditor.MapEditor.finishdrawing
    quarkpy.mapeditor.MapEditor.finishdrawing = tagfinishdrawing
  mapeditor().invalidateviews()         # redraw the map

# this doesn't work, gives NameError on mapeditor, why?
#quarkpy.mapeditor.Mapeditor.stupid = 1

def ClearTagClick (m):
  "clears tag on menu-click"
  editor = mapeditor()
  if editor is None: return
  if gettagged(editor) is None: return
  editor.tagging.tagged = None
  global mencleartag
  mencleartag.state = qmenu.disabled
  global menglueside
  menglueside.state = qmenu.disabled
  editor.invalidateviews()


def GlueSideClick(m):
  "glues selection or current side handle to tagged side"
  editor = mapeditor()
  if editor is None: return
  sides = editor.layout.explorer.sellist
  try:
    sides = [m.side]
  except (AttributeError): pass
  tagged = gettagged(editor)
  if (len(sides) < 1):
    quarkx.msgbox("Nothing to "+`what`, MT_WARNING, MB_OK)
    return
  for side in sides:
    if (side.type != ":f"):
      quarkx.msgbox("Some selected object is not a face", MT_ERROR, MB_OK) 
      return
  editor.invalidateviews()
  gluit = 1
  ActionString = "glue to tagged"
  undo = quarkx.action()
  for side in sides:
    fulcrum = side.origin
    try:
      fulcrum = m.fulcrum
      gluit = 0
      ActionString = "Align to tagged"
    except (AttributeError) : pass
    new=side.copy()
    #
    # if necessary (it usually is), flip normal of new side
    #
    if new.normal*tagged.normal < 0:
      new.distortion(-tagged.normal, fulcrum)
    else: new.distortion(tagged.normal,side.origin)
    if gluit:
      new.translate(tagged.origin-new.origin)
    #
    # only do swap if it won't break any polys
    #
    if checkpolys(side, new):
       undo.exchange(side, new)
    else:
      quarkx.msgbox("That operation would trash a brush", MT_ERROR, MB_OK) 
      return
  editor.layout.explorer.sellist = []
  undo.ok(editor.Root, ActionString)


#
#  maybe a candidate for quarkx?
#
def checkpolys(old, new):
  "checks that swapping new for old breaks none of old's polys"
  polys = old.faceof
  for poly in polys:
    clone = quarkx.newobj("poly:p")
    for face in poly.faces:
      if face is old:
        clone.appenditem(new.copy())
      else:
        clone.appenditem(face.copy())
    if clone.broken:
      return 0
  return 1



#
#  Set up command menus.  Maybe junk these for buttons, or only
#   use right-mouse click?
#


mencleartag = quarkpy.qmenu.item("&Clear Tag", ClearTagClick)
mencleartag.state = qmenu.disabled
menglueside = quarkpy.qmenu.item("&Glue to Tagged", GlueSideClick)
menglueside.state = qmenu.disabled

quarkpy.mapcommands.items.append(quarkpy.qmenu.item("&Tag Side", TagSideClick))
quarkpy.mapcommands.items.append(mencleartag)
quarkpy.mapcommands.items.append(menglueside)
