
########################################################
#
#                      Mad Selector Plugin
#                      v1.0, Nov 18, 1998
#                      works with Quark51            
#
#
#        by tiglari@hexenworld.com, with advice
#          and code snippets from Armin Rigo
#     
#
#   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
#
#   This is a preliminary version.  It's supposed to act
#   in two different ways:
#
#     if what's selected is a face, it selects all adjacent faces and
#        gives you the multiple selection handles.
#
#     if what's selected is a poly or group, it draws adjacent faces
#        in red, but leaves you with the normal drag handles; then
#        when you stop dragging yo get the multiple selection handles
#        (is this a bug or a feature?  You tell me!)
#
#     On smallish but not tiny maps, selection-extension from a brush
#        takes appreciable time, maybe it would be help to put some
#        of the routines into a .dll
#
#     In general, this piece of code looks to me to be very
#        wild, messy & dangerous, advice on how to make it
#        better would be much appreciated.
#
##########################################################


Info = {
   "plug-in":       "Mad Selector",
   "desc":          "Extending selection to adjacent & coplanar faces",
   "date":          "18 Nov 1998",
   "author":        "tiglari",
   "author e-mail": "tiglari@hexenworld.com",
   "quark":         "Version 5.1" }



import quarkx
import quarkpy.mapmenus
import quarkpy.mapentities
import quarkpy.qmenu
import quarkpy.mapeditor
import quarkpy.mapcommands
from quarkpy.maputils import *





###################################
#
# right-mouse menus for faces.  Messes up selected brish
#
###################################

oldfacemenu = quarkpy.mapentities.FaceType.menu.im_func
	
def extmenuitem(String, ClickFunction,o):
  "make a menu-item with a side attached"
  item = qmenu.item(String, ClickFunction)
  item.obj = o
  return item

def madfacemenu(o, editor):
  "the new right-mouse menu for faces"
  menu = oldfacemenu(o, editor)
  menu[:0] = [extmenuitem("Extended Selection",ExtendSelClick,o)]
  return menu  

quarkpy.mapentities.FaceType.menu = madfacemenu

###################################
#
# right-mouse menus for polys
#
###################################

oldpolymenu = quarkpy.mapentities.PolyhedronType.menu.im_func
	
def madpolymenu(o, editor):
  "the new right-mouse menu for faces"
  menu = oldpolymenu(o, editor)
  menu[:0] = [extmenuitem("Extended Selection",ExtendSelClick,o)]
  return menu  

quarkpy.mapentities.PolyhedronType.menu = madpolymenu

###################################
#
# right-mouse menus for groups
#
###################################

oldgroupmenu = quarkpy.mapentities.GroupType.menu.im_func
	
def madgroupmenu(o, editor):
  "the new right-mouse menu for groups"
  menu = oldgroupmenu(o, editor)
  menu[:0] = [extmenuitem("Extended Selection",ExtendSelClick,o)]
  return menu  

quarkpy.mapentities.GroupType.menu = madgroupmenu


#################################
#
#  Hacking drag
#
##################################

olddrag = quarkpy.qhandles.CenterHandle.drag

def maddrag(self, v1, v2, flags, view):
  (old, new) = olddrag(self, v1, v2, flags, view)
  if len(new) == 1:
    try:
      editor=mapeditor()
      madsel = editor.madsel
      #
      # complex shenannigans to track dragged selections
      #  'twould be cool if someone told me a better way
      #
      if old[0] is madsel.orig:
        madsel.neworig = new[0]
        #
        # assumes that new faces will have the same positions in
        # the subitems lists as the corresponding old ones (???)
        #
        oldfaces = old[0].findallsubitems("",":f")
#        quarkx.msgbox(`len(oldfaces)`,MT_INFORMATION,MB_OK)
        newfaces = new[0].findallsubitems("",":f")
        newpairs=[]
        for pair in madsel.extra:
          newface = newfaces[oldfaces.index(pair[0])]
          delta = newface.origin-pair[0].origin
#          quarkx.msgbox(`delta`,MT_INFORMATION, MB_OK)
          newcofaces=[]
          for face in pair[1]:
            newcoface = face.copy()
            newcoface.translate(delta)
            new.append(newcoface)
            old.append(face)
            newcofaces.append(newcoface)
          newpair = (newface, newcofaces)
          newpairs.append(newpair)
        madsel.newextra = newpairs  
    except (AttributeError): pass
  return (old, new)

quarkpy.qhandles.CenterHandle.drag = maddrag


####################################
#
# hacking finishdrawing
#
#####################################

def getmadsel(obj):
  "safe fetching of a mad selection"
  try:
     return obj.madsel
  except (AttributeError): return None


def madfinishdrawing(self, view): 
  "the new finishdrawning routine"
  Madsel.oldfinishdrawing(self, view)
  editor = mapeditor()
  madsel = getmadsel(editor)
  if madsel is None: return
  #
  # clear all if original selection no longer in map
  #   
#  if not tigutes.checktree(editor.Root,editor.madsel.orig):
  if editor.layout.explorer.sellist == [editor.madsel.neworig]:
    editor.madsel.orig = editor.madsel.neworig
    editor.madsel.extra = editor.madsel.newextra
  if not editor.layout.explorer.sellist == [editor.madsel.orig]: 
    editor.madsel = None
    return
  cv = view.canvas()
  cv.pencolor = MapColor("Tag")
  for pair in madsel.extra:
#    quarkx.msgbox(`len(pair[1])`,MT_INFORMATION,MB_OK)
    for face in pair[1]:
      for vtx in face.vertices: # is a list of lists
        p2 = view.proj(vtx[-1])  # the last one
        for v in vtx:
          p1 = p2
          p2 = view.proj(v)
          cv.line(p1,p2)


class Madsel:
  def __init__(self, orig):
    self.orig = orig
    self.neworig = None
    self.extra = []
  oldfinishdrawing = None    

def swapfinishdrawing(editor):
  if Madsel.oldfinishdrawing is None:
    Madsel.oldfinishdrawing = quarkpy.mapeditor.MapEditor.finishdrawing
    quarkpy.mapeditor.MapEditor.finishdrawing = madfinishdrawing

#####################################3
#
# and finally the point of it all ...
#
#######################################



def ExtendSelClick(m):
  "extends the selection to adjacent sides"
  editor = mapeditor()
  if editor is None: return
  selection = editor.layout.explorer.sellist
  if len(selection) == 1:    
    sel = selection[0]
    try:
      item = m.obj
      if item.type == ":f":
        sel = item
#        editor.layout.explorer.sellist = [item]
#        quarkx.msgbox("sothoth",MT_INFORMATION,MB_OK)
#        editor.invalidateviews()
#        return
    except (AttributeError) : pass
#        quarkx.msgbox("yoohoo",MT_INFORMATION,MB_OK)
    if sel.type == ":f":
      editor.layout.explorer.sellist = adjacent2side(editor.Root, sel)
    else:
      swapfinishdrawing(editor)
      editor.madsel = Madsel(sel)
      faces = sel.findallsubitems("",":f")
      for face in faces:
        adj = adjacent2side(editor.Root, face)
        adj.remove(face)
        if len(adj)>0:
          editor.madsel.extra.append((face, adj))
      editor.invalidateviews()
  else:
    quarkx.msgbox("No multiple selections",MT_INFORMATION,MB_OK)
   
def ExtendSelClick2(m):
  "extends the selection to adjacent sides (old version)"
  editor = mapeditor()
  if editor is None: return
  selection = editor.layout.explorer.sellist
  for thing in selection:
    #
    # if there's a non-face selection, extend silently if the
    #   selection is of length 1.
    #
    if thing.type != ":f":
      if len(selection) > 1:
        sides = []
        for obj in selection:
          sides[-1:] = obj.findallsubitems("",":f")
        selection[-1:] = adjacent2sides(editor.Root, sides)
        editor.layout.explorer.sellist = selection
      else:
        swapfinishdrawing(editor)
        sides = selection[0].findallsubitems("",":f")
        cosel = adjacent2sides(editor.Root, sides)
        cosel = filter(lambda x, sids=sides: not x in sids, cosel)
        editor.madsel = Madsel(selection[0], cosel)
        ln = len(cosel)
#        quarkx.msgbox(`ln` + "sides", MT_INFORMATION, MB_OK)
        editor.invalidateviews()
      break
  else:
    newsides = adjacent2sides(editor.Root, selection)
    editor.layout.explorer.sellist = newsides


def side2pair(side):
  return (side, prism_from_bottom(side)) 

def adjacent2sides(tree, sides, errmsg=0):
  "returns the side adjacent to some sides"
  newsides=[]
  for side in sides:
    #
    # maybe this isn't looking into entities, maybe getting the
    #  brushes of brush-owning entities should be a parameter?
    #
    if side.type != ":f":
      if errmsg:
        quarkx.msgbox(errmsg,MT_ERROR,MB_OK)
      return
    newsides[-1:] = adjacent2side(tree, side)
  return newsides


def adjacent2side(tree, side):
    "returns the sides adjacent to a side"
    parsides = getsub_coplanar(tree, side)
    firstpair, parpairs = side2pair(side), map(side2pair, parsides) 
    if firstpair[1].type==":g":
      polys=firstpair[1].findallsubitems("",":p")
      copairs=map(lambda x,s=firstpair[0]:(s,x), polys) 
    else:
      copairs=[firstpair]
    extend_copairs(copairs,parpairs)
    return map(lambda p: p[0], copairs)


def getsub_coplanar(obj, side):
  result = obj.findallsubitems("",":f")
  result = filter(lambda x, y=side:coplanar(y,x), result)
  result.remove(side)
  return result

def extend_copairs(copairs, parpairs):
  for co in copairs:
    co[1].inflate(.01) # make sure this happens only once!
    #
    # this loop assumes that each pair's 2nd member is either a
    #  group with first mem as shared face, or a poly.  As it
    #  iterates, new stuff to apply to gets added to the end of
    #  copairs.  The Python tutorial warns against this, but it
    #  seems to work.  Can't see why it shouldn't.
    #
    for par in parpairs:
      if par[1].type == ":g":
        #
        # if any prism in the group touches, they're all added to
        #  copairs
        #
        sublist = par[1].findallsubitems("",":p")
        for sub in sublist:
          if co[1].intersects(sub):
            for item in sublist:
              copairs.append((par[0], item))
            parpairs.remove(par)
            break
      #
      # and at last the regular case
      #
      elif co[1].intersects(par[1]):
        copairs.append(par)
        parpairs.remove(par)


def test_overlap(f1, f2):
  "tests if faces are coplanar and overlap"
  if not coplanar(f1, f2): return 0
  p1, p2 = prism_from_bottom(f1), prism_from_bottom(f2)
  p1.inflate(.01)
  if p1.intersects(p2):
    quarkx.msgbox("yes",MT_INFORMATION,MB_YES)
  else:
    quarkx.msgbox("no",MT_INFORMATION,MB_YES)

#
# the tolerances here are just guesses, maybe some actual math analysis
#   would pay off.
#
def coplanar(f1, f2):
  "the two faces are coplanar"
  if math.fabs(f1.normal*f2.normal) > .99999:
    if math.fabs(f1.normal*(f1.origin-f2.origin)) < .0000001:
      return 1
  else: return 0



def prism_from_bottom(face, height = 64):
  "face is a face taken as the bottom of the prism"
  bottom = face.copy()
  top = bottom.copy()
  top.shortname = "top"
  top.swapsides()
  norm = top.normal
  top.translate(norm*height)
  polys = face.faceof
  if len(polys) == 1:
    prism = quarkx.newobj("prism:p")
    for wall in face.extrudeprism(polys[0]):
      prism.appenditem(wall)
    prism.appenditem(top)
    prism.appenditem(bottom)
    if prism.broken: return None
  else:
    prism = quarkx.newobj("prism:g")
#
# I still don't understand why these right below don't work,
#  but the copies have to be made instead!  Insufficient rpms
#  in the 3rd neuron.
#
#    prism.appenditem(top)
#    prism.appenditem(bottom)
    for poly in polys:
      cylin = quarkx.newobj("subpr:p")
      for wall in face.extrudeprism(poly):
         cylin.appenditem(wall)
      prism.appenditem(cylin)
      cylin.appenditem(top.copy())
      cylin.appenditem(bottom.copy())
      if cylin.broken: return None
  return prism

 
 
quarkpy.mapcommands.items.append(quarkpy.qmenu.item("Extend Selection", ExtendSelClick))
