"""   QuArK  -  Quake Army Knife

Bezier shape makers
"""

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

########################################################
#
#                          Curves Plugin
#                          v1.0, May 2000
#                      works with Quark 6.0b2
#
#
#                    by tiglari@hexenworld.com
#
#   You may freely distribute modified & extended versions of
#   this plugin as long as you give due credit to tiglari &
#   Armin Rigo. (It's free software, just like Quark itself.)
#
#   Please notify bugs & improvements to tiglari@hexenworld.com
#
###
##########################################################

Info = {
   "plug-in":       "Curves plugin",
   "desc":          "Making curves from brushes, etc.",
   "date":          "1 May 2000",
   "author":        "tiglari",
   "author e-mail": "tiglari@hexenworld.com",
   "quark":         "Version 6.0b2" }


import quarkx
import quarkpy.mapmenus
import quarkpy.mapentities
import quarkpy.mapeditor
import quarkpy.mapcommands
import quarkpy.mapoptions
import quarkpy.maphandles
import quarkpy.dlgclasses
import quarkpy.mapduplicator
StandardDuplicator = quarkpy.mapduplicator.StandardDuplicator
from quarkpy.maputils import *

import quarkpy.mapbezier
from quarkpy.b2utils import *
from quarkpy.perspective import *

#############################
#
#  MAJOR SECTIONS
#
#  - image builders: implementation of buildimages for the
#      shape-builders
#
#  - duplicators: the duplicator code
#
#  - menus
#
# -- Image builders
#
#    many of these below should probably be stuck inside
#    the appropriate cap/bevel/columnImages method
#

def makearchfacecp(bl, tl, tr, br):
    mid = (tl+tr)/2.0
    cp = [[bl, tl, mid, tr, br],
          [(bl+tl)/2.0, tl, mid, tr, (br+tr)/2.0],
          [tl, (tl+mid)/2.0, mid, (mid+tr)/2.0, tr]]
    return cp

def makecapfacecp(bl, tl, tr, br):
    bm = (bl+br)/2.0
    tm = (tl+tr)/2.0
    cp = [[bl, (bl+bm)/2.0, bm, (bm+br)/2.0, br],
          [bl, (bl+bm)/2.0, (tm+bm)/2.0, (bm+br)/2.0, br],
          [bl, tl, tm, tr, br]]
    return cp


def smallerarchbox(box, thick):
    "returns a box for arch, thick smaller than input box"
    fi = thick*(box["brf"]-box["blf"]).normalized
    bi = thick*(box["brb"]-box["blb"]).normalized
    fd = thick*(box["brf"]-box["trf"]).normalized
    bd = thick*(box["brb"]-box["trb"]).normalized
    box2 = {}
    for (corner, delta) in (("blf",fi), ("blb",bi),
            ("tlf",fi+fd), ("tlb",fi+bd), ("trf",fd-fi), ("trb",bd-bi),
            ("brf",-fi), ("brb",-bi)):
        box2[corner]=box[corner]+delta
    return box2

def smallerbevelbox(box, thick):
    "returns a box for bevel, thick smaller than input box"
    def gap(goal, source, box=box, thick=thick):
        return thick*(box[goal]-box[source]).normalized
    rf = gap("tlf", "trf")
    rb = gap("tlb", "trb")
    lb = gap("tlf", "tlb")
    zip = quarkx.vect(0,0,0)
    box2 = {}
    for (corner, delta) in (("blf",zip), ("blb",lb),
            ("tlf",zip), ("tlb",lb), ("trf",rf), ("trb",rb+lb),
            ("brf",rf), ("brb",rb+lb)):
        box2[corner]=box[corner]+delta
    return box2

def smallercolumnbox(box, thick):
    "returns a box for cp;i,m, thick smaller than input box"
    def gap(goal, source, box=box, thick=thick):
        return thick*(box[goal]-box[source]).normalized
    f = gap("tlf", "trf")
    b = gap("tlb", "trb")
    l = gap("tlf", "tlb")
    r = gap("trf", "trb")
    box2 = {}
    for (corner, delta) in (
          ("tlf",-f-l), ("blf",-f-l),
          ("tlb",-b+l), ("blb",-b+l),
          ("trf",f-r), ("brf",f-r),
          ("trb",b+r), ("brb",b+r)):
        box2[corner]=box[corner]+delta
    return box2


def archline(pd, a, b, c, d):
    "returns 5-tuple with middle halfway between b and c"
    return [pd[a], pd[b], (pd[b]+pd[c])/2, pd[c], pd[d]]


def archcurve(pd):
    cp = cpFrom2Rows(archline(pd, "blf", "tlf", "trf", "brf"),
                      archline(pd, "blb", "tlb", "trb", "brb"))
    return cp


def capImages(o, editor, inverse=0, lower=0, onside=0, open=0, thick=0, faceonly=0,
    stretchtex=0, nofront=0, noback=0, noinner=0, noouter=0, (subdivide,)=1):
  "makes a 'cap' (or arch) on the basis of brush o"
  #
  # Make dictionary of faces u/d/f/b/r/l
  #
  o.rebuildall()
  fdict = faceDict(o)
  if fdict is None:
    return
  #
  # make dictionary of points, 'bottom left front' etc.
  # this one's name is short because we refer to it so often
  #
  if onside:
      fdict = facedict_rflip(facedict_rflip(facedict_rflip(fdict)))
  pd = pointdict(vtxlistdict(fdict,o))
  if lower:
      pd = pointdict_vflip(pd)
      pd = pointdict_hflip(pd)
      texface = fdict["d"].copy()
  else:
      texface = fdict["u"].copy()
  #
  # make the basic inner curved face, a 3x5 quilt
  #
  cp = archcurve(pd)
  #
  # project cps from face to patch (flat projection, distorted)
  #
  cp = texcpFromFace(cp, texface, editor)
  #
  # adjust down sides if wanted
  #
  if not stretchtex:
      if lower:
        right, left = fdict["l"], fdict["r"]
      else:
        right, left = fdict["r"], fdict["l"]
      #
      # For the right and left sides of the guide brush ...
      #
      for side, fulcrum, edge in ((right, "trf", 4), (left, "tlf", 0)):
          #
          #  make a copy of the 'top' (where the texture is being
          #   taken from)
          #
          newside = texface.copy()
          #
          # rotate around the front side corner, so that it is coincident
          #  with the side
          #
          newside.distortion(side.normal, pd[fulcrum])
          #
          # a copy of the patch's control-points with the rotated
          #  face's texture projected onto it (would look good at
          #  one end, terrible at the other)
          #
          cp2 = texcpFromFace(cp, newside, editor)
          #
          # Now set the real patch control points along the edge to
          #  the new ones.
          #
          for index in range(3):
              cp[index][edge]=cp2[index][edge]
  #
  # We have now set the cp's for both edges of the patch correctly,
  #  so we smooth things out
  #
  cp = undistortRows(cp)
  cp = undistortColumns(cp)
  inner = quarkx.newobj('inner:b2')
  cp = subdivideRows(subdivide,cp)
  inner.cp = cp
  inner["tex"] = texface["tex"]
  if thick:
      pd2 = smallerarchbox(pd, thick)
      cp2 = archcurve(pd2)
      cp2 = subdivideRows(subdivide,cp2)
      inner.shortname = "outer"
      inner2=quarkx.newobj("inner:b2")
      cp2 = texcpFromCp(cp2, cp)
      inner2.cp = cp2
      inner2["tex"] = inner["tex"]
      #
      # seams
      #
      seams = []
      if not open:
          if not nofront:
              fseam = b2From2Rows(archline(pd, "brf", "trf", "tlf", "blf"),
                           archline(pd2,"brf", "trf", "tlf", "blf"),
                          fdict["f"], "front")
              fseam.cp = subdivideRows(subdivide,fseam.cp)
              seams.append(fseam)
          if not noback:
              bseam = b2From2Rows(archline(pd, "blb", "tlb", "trb", "brb"),
                           archline(pd2,"blb", "tlb", "trb", "brb"),
                           fdict["b"], "back")
              bseam.cp = subdivideRows(subdivide,bseam.cp)
              seams.append(bseam)
      if lower:
        inner.swapsides()
        bseam.swapsides()
        fseam.swapsides()
      else:
        inner2.swapsides()
      inners = []
      if not faceonly:
          if not noouter:
              inners.append(inner)
          if not noinner:
              inners.append(inner2)
      return inners + seams
  # end if thick

#  if lower:
 #     inner.swapsides()
  if inverse:
     inner.swapsides()
  if open:
      return [inner]
  if inverse:
     fcp = makearchfacecp(pd["blf"],pd["tlf"],pd["trf"],pd["brf"])
     bcp = makearchfacecp(pd["blb"],pd["tlb"],pd["trb"],pd["brb"])
  else:
     fcp = makecapfacecp(pd["blf"],pd["tlf"],pd["trf"],pd["brf"])
     bcp = makecapfacecp(pd["blb"],pd["tlb"],pd["trb"],pd["brb"])
#  if lower:
#      fcp = transposeCp(fcp)
#  else:
  if subdivide>1:
      if not nofront:
          fcp = subdivideRows(subdivide, fcp)
      if not noback:
          bcp = subdivideRows(subdivide, bcp)
  bcp = transposeCp(bcp)
  faces = []
  if not nofront:
      front = b2FromCpFace(fcp, 'front', fdict["f"], editor)
      faces.append(front)
  if not noback:
      back = b2FromCpFace(bcp,'back', fdict["b"], editor)
      faces.append(back)
  if faceonly:
      return faces
  return [inner]+faces


def bevelImages(o, editor, inverse=0, lower=0, left=0, standup=0, open=0, thick=0,
  faceonly=0, stretchtex=0, notop=0, nobottom=0, noinner=0, noouter=0, (subdivide,)=1):
  "makes a bevel/inverse bevel on the basis of brush o"
  o.rebuildall()
  #
  # make a dictionary where faces are indexed by the first letter
  # of their name (front|back|left|right|up|down)
  #
  fdict = faceDict(o)
  if fdict is None:
    return
  if standup:
      fdict = facedict_rflip(fdict)
  if lower:
      fdict = facedict_fflip(fdict)
  #
  # get a dict of the vertices indexed by [t|b][r|l][f|b]
  #
  pd = pointdict(vtxlistdict(fdict,o))
  #
  # interchange left and right vertices for left bevel
  #
  if left:
      pd = pointdict_hflip(pd)
  #
  # make patch controlpoints, rows starting at the back, curving
  #  towards front (for regular and left versions).  For left bevels,
  #  'l' vertices will be right and vice versa
  #
  def bevelcurve(pd):
#      return cpFrom2Rows(subdivideLine(2, pd["tlb"], pd["trb"],pd["trf"]),
#                         subdivideLine(2, pd["blb"],pd["brb"],pd["brf"]))
      return cpFrom2Rows([pd["tlb"], pd["trb"],pd["trf"]],
                         [pd["blb"],pd["brb"],pd["brf"]])
  cp = bevelcurve(pd)
  inner = quarkx.newobj('inner:b2')
  #
  # project the texture from the back face onto the patch
  #
  cp = texcpFromFace(cp, fdict["b"], editor)
  if not stretchtex:
      newside = fdict["b"].copy()
      #
      # fdict has not been flipped for left brushes
      #
      if left:
          newside.distortion(fdict["l"].normal,pd["trb"])
      else:
          newside.distortion(fdict["r"].normal,pd["trb"])
      cp2 = texcpFromFace(cp, newside, editor)
      debug("cp: ")
      writecps(cp)
#      debug("cp2: ")
#      writecps(cp2)
      for index in range(3):
        cp[index][2]=cp2[index][2]

  cp = undistortRows(cp)
  cp = undistortColumns(cp)
  cp = subdivideRows(subdivide,cp)
  inner.cp = cp
  inner["tex"] = fdict["b"]["tex"]
  if thick:
      inner.shortname="outer"
      pd2 = smallerbevelbox(pd, thick)
      cp2 = bevelcurve(pd2)
      inner2=quarkx.newobj("inner:b2")
      inner2.cp = texcpFromCp(subdivideRows(subdivide,cp2), cp)
      inner2["tex"]=inner["tex"]
      tseam = b2From2Rows([pd["trf"], pd["trb"], pd["tlb"]],
                       [pd2["trf"], pd2["trb"], pd2["tlb"]],
                        fdict["u"],"top")
      bseam = b2From2Rows([pd["blb"], pd["brb"], pd["brf"]],
                       [pd2["blb"], pd2["brb"], pd2["brf"]],
                        fdict["d"],"bottom")
      tseam.cp = subdivideRows(subdivide, tseam.cp)
      bseam.cp = subdivideRows(subdivide, bseam.cp)
      if left:
          inner.swapsides()
          tseam.swapsides()
          bseam.swapsides()
      else:
          inner2.swapsides()
      if lower:
          inner.swapsides()
          inner2.swapsides()
          tseam.swapsides()
          bseam.swapsides()
      seams = [tseam, bseam]
      if notop:
          seams.remove(tseam)
      if nobottom:
          seams.remove(bseam)
      if faceonly:
         return seams
      inners = [inner, inner2]
      if noinner:
          inners.remove(inner2)
      if noouter:
          inners.remove(inner)
      return inners+seams
  if left:
    inner.swapsides()
  if inverse:
    inner.swapsides()
  if lower:
     inner.swapsides()
  if open:
      return [inner]
  if inverse:
      tcp = cpFrom2Rows([pd["tlb"], pd["trb"], pd["trf"]],
                         [pd["trb"], pd["trb"], pd["trf"]])
      bcp = cpFrom2Rows([pd["brf"], pd["brb"], pd["blb"]],
                         [pd["brf"], pd["brb"], pd["brb"]])
  else:
      tcp = subdivideRows(subdivide,cpFrom2Rows([pd["trf"], pd["trb"], pd["tlb"]],
                         [pd["tlf"], pd["tlf"], pd["tlb"]]))
      bcp = subdivideRows(subdivide,cpFrom2Rows([pd["blb"], pd["brb"], pd["brf"]],
                         [pd["blb"], pd["blf"], pd["blf"]]))
  top = b2FromCpFace(tcp,"top",fdict["u"],editor)
  bottom = b2FromCpFace(bcp,"bottom",fdict["d"],editor)
  if left:
    top.swapsides()
    bottom.swapsides()
  if lower:
    top.swapsides()
    bottom.swapsides()
  faces = [bottom, top]
  if notop:
      faces.remove(top)
  if nobottom:
      faces.remove(bottom)
  if faceonly:
    return faces
  return [inner] + faces


def circleLine(p0, p1, p2, p3):
    return [(p0+p1)/2, p1, (p1+p2)/2, p2, (p2+p3)/2, p3,
            (p3+p0)/2, p0, (p0+p1)/2]

def columnImages(o, editor, inverse=0, open=0, thick=0, stretchtex=0, bulge=(.5,1),
      funnel=None, faceonly=0, notop=0, nobottom=0, noinner=0, noouter=0, circle=0, (subdivide,)=1):
    "makes a column on the basis of brush o"
    if circle:
        subfunc=arcSubdivideLine
    else:
        subfunc=None
    o.rebuildall()
    fdict = faceDict(o)
    if fdict is None:
        return
    pdo = pointdict(vtxlistdict(fdict,o))

    if funnel is not None:

        def warpbox(pd, pdo=pdo,funnel=funnel):
            pd2 = {}
            for (i, p0, p1, p2, p3) in ((0, "tlf", "tlb", "trb", "trf"),
                                        (1, "blf", "blb", "brb", "brf")):
                c = (pd[p0]+pd[p1]+pd[p2]+pd[p3])/4.0
                for p in (p0, p1, p2, p3):
                    pd2[p] = c+funnel[i]*(pd[p]-c)
            return pd2

        pd = warpbox(pdo)
    else:
        pd = pdo


    def curveCp(pd, bulge=bulge):
        cp = cpFrom2Rows(circleLine(pd["trf"], pd["tlf"], pd["tlb"], pd["trb"]),
                        circleLine(pd["brf"], pd["blf"], pd["blb"], pd["brb"]),bulge)
        return cp

    cp = curveCp(pd)

    def makeTube (cp, pd, oldface, pdo=pdo, fdict=fdict, stretchtex=stretchtex, subfunc=subfunc, subdivide=subdivide,editor=editor):
        cp2 = interpolateGrid(pdo["tlb"], pdo["trb"], pdo["blb"], pdo["brb"], 3, 9)
        cp2 = texcpFromFace(cp2, oldface, editor)
        cp = texcpFromCp(cp, cp2)
        if subdivide>1:
            cp = subdivideRows(subdivide,cp,subfunc)
        if not stretchtex:
            for (facekey, corner) in (("r", "trb"),("f", "trf"),("l","tlf")):
                newface=oldface.copy()
                newface.setthreepoints(oldface.threepoints(2),2)
                newface.distortion(fdict[facekey].normal,pdo[corner])
                oldface = newface
            cp3 = interpolateGrid(pdo["tlf"], pdo["tlb"], pdo["blf"], pdo["blb"])
            cp3 = texcpFromFace(cp3, oldface, editor)
            for i in range(3):
                cp[i][8] = quarkx.vect(cp[i][8].xyz+cp3[i][2].st)
#    squawk(`cp`)
        cp = undistortRows(cp)
        inner = quarkx.newobj("inner:b2")
        inner.cp = cp
        inner["tex"]=oldface["tex"]
        return inner

    inner = makeTube(cp, pd, fdict["b"])
    if thick:
        pd2 = smallercolumnbox(pd, thick)
        cp2 = curveCp(pd2)
        inner2 = makeTube(cp2, pd2, fdict["f"])
        inner2.shortname="inner"
        inner.shortname="outer"
        tcp = cpFrom2Rows(cp[0],cp2[0])
        bcp = cpFrom2Rows(cp[2], cp2[2])
        if subdivide>1:
            tcp = subdivideRows(subdivide,tcp, subfunc)
            bcp = subdivideRows(subdivide,bcp, subfunc)
        top = b2FromCpFace(tcp,"top",fdict["u"],editor)
        top.swapsides()
        bottom = b2FromCpFace(bcp,"bottom",fdict["d"],editor)
        inner2.swapsides()
        seams = [top, bottom]
        if notop:
            seams.remove(top)
        if nobottom:
            seams.remove(bottom)
        if faceonly:
           return seams
        inners = [inner, inner2]
        if noinner:
            inners.remove(inner2)
        if noouter:
            inners.remove(inner)
        return inners+seams

    if open:
       if inverse:
          inner.swapsides()
       return [inner]


    if inverse:

        def squareFromCircle(row): # row = 9 pts, cp's for circle
            # first not used, passed to reduce index confusion
            def halfSquare(hr): # hr=half-row excluding center
                return [hr[1], hr[1], hr[2], hr[3], hr[3]]

            return halfSquare(row[:4]), halfSquare(row[4:8])

        def faces(circline, borderfunc, name, texface, subdivide=subdivide,subfunc=subfunc):
            out0, out1 = borderfunc(circline)
            b2a = b2From2Rows(out0, circline[0:5],texface,name+'0',subdivide=subdivide,subfunc=subfunc)
            b2b = b2From2Rows(out1, circline[4:9],texface,name+'1',subdivide=subdivide,subfunc=subfunc)
            return b2a, b2b

        topa, topb = faces(cp[0],squareFromCircle,'top',fdict['u'])
        inner.swapsides()
        topa.swapsides()
        topb.swapsides()
        bottoma, bottomb = faces(cp[2],squareFromCircle,'bottom',fdict['d'])
        result = [inner, topa, topb, bottoma, bottomb]
        if faceonly:
            result.remove(inner)
        if notop:
            result.remove(topa)
            result.remove(topb)
        if nobottom:
            result.remove(bottoma)
            result.remove(bottomb)
    else:
        def center(v):
            c = (v[1]+v[3]+v[5]+v[7])/4.0
            return map(lambda x,c=c:c,range(9))

        top = b2From2Rows(center(cp[0]),cp[0],fdict['u'],'top',subdivide=subdivide,subfunc=subfunc)
        bottom = b2From2Rows(cp[2], center(cp[2]),fdict['d'],'bottom',subdivide=subdivide,subfunc=subfunc)
        result = [inner, top, bottom]
        if faceonly:
            result.remove(inner)
        if notop:
            result.remove(top)
        if nobottom:
            result.remove(bottom)

    return result


def images(buildfn, args):
    if quarkx.setupsubset(SS_MAP, "Options")["Developer"]:
        return apply(buildfn, args)
    else:
        try:
            return apply(buildfn, args)
        except:
            return []

#
#  --- Duplicators ---
#

class CapDuplicator(StandardDuplicator):

  def buildimages(self, singleimage=None):
    if singleimage is not None and singleimage>0:
      return []
    editor = mapeditor()
    inverse, lower, onside, open, thick, faceonly, stretchtex, nofront, noback, noinner, noouter, subdivide = map(lambda spec,self=self:self.dup[spec],
      ("inverse", "lower", "onside", "open", "thick", "faceonly", "stretchtex",
         "nofront", "noback", "noinner", "noouter", "subdivide"))
    if thick:
      thick, = thick
    if subdivide is None:
        subdivide=1,
    list = self.sourcelist()
    for o in list:
      if o.type==":p": # just grab the first one, who cares
         return images(capImages, (o, editor, inverse, lower, onside, open, thick,
           faceonly, stretchtex, nofront, noback, noinner, noouter, subdivide))
    return []


class BevelDuplicator(StandardDuplicator):

  def buildimages(self, singleimage=None):
    if singleimage is not None and singleimage>0:
      return []
    editor = mapeditor()
    inverse, lower, left, standup, sidetex, open, thick, faceonly, stretchtex, notop, nobottom, noinner, noouter, subdivide = map(lambda spec,self=self:self.dup[spec],
      ("inverse", "lower", "left", "standup", "sidetex", "open", "thick", "faceonly", "stretchtex",
         "notop", "nobottom", "noinner", "noouter", "subdivide"))
    if thick:
      thick, = thick
    list = self.sourcelist()
    if subdivide is None:
        subdivide=1,
    for o in list:
      if o.type==":p": # just grab the first one, who cares
           return images(bevelImages, (o, editor, inverse, lower, left, standup, open, thick,
              faceonly, stretchtex, notop, nobottom, noinner, noouter, subdivide))
    return []

class ColumnDuplicator(StandardDuplicator):

  def buildimages(self, singleimage=None):
    if singleimage is not None and singleimage>0:
      return []
    editor = mapeditor()
    inverse, open, thick, stretchtex, bulge, funnel, faceonly,notop,nobottom, noinner, noouter, circle, subdivide = map(lambda spec,self=self:self.dup[spec],
      ("inverse", "open", "thick", "stretchtex", "bulge", "funnel", "faceonly", "notop", "nobottom", "noinner","noouter",  "circle", "subdivide"))
    if thick:
      thick, = thick
    list = self.sourcelist()
    if subdivide is None:
        subdivide=1,
    for o in list:
      if o.type==":p": # just grab the first one, who cares
           return images(columnImages, (o, editor, inverse, open, thick, stretchtex, bulge,funnel,
             faceonly,notop,nobottom, noinner, noouter, circle,subdivide))
    return []

quarkpy.mapduplicator.DupCodes.update({
  "dup cap":     CapDuplicator,
  "dup bevel":   BevelDuplicator,
  "dup column":  ColumnDuplicator
})

#
#  --- Menus ---
#

def curvemenu(o, editor, view):

  def makecap(m, o=o, editor=editor):
      dup = quarkx.newobj(m.mapname+":d")
      dup["macro"]="dup cap"
      if m.inverse:
        dup["inverse"]=1
      dup.appenditem(m.newpoly)
      undo=quarkx.action()
      undo.exchange(o, dup)
      if m.inverse:
        editor.ok(undo, "make arch")
      else:
        editor.ok(undo, "make cap")
      editor.invalidateviews()


  def makebevel(m, o=o, editor=editor):
      dup = quarkx.newobj("bevel:d")
      dup["macro"]="dup bevel"
      dup["inverse"]=1
      if m.left:
        dup["left"]=1
      dup["open"]=1  # since this is normally rounding a corner with wall & ceiling"
      dup.appenditem(m.newpoly)
      undo=quarkx.action()
      undo.exchange(o, dup)
      if m.left:
        editor.ok(undo, "make left corner")
      else:
        editor.ok(undo, "make right corner")

  def makecolumn(m, o=o, editor=editor):
      dup = quarkx.newobj("column:d")
      dup["macro"]="dup column"
      dup["open"]=1  # since this is normally rounding a corner with wall & ceiling"
      dup.appenditem(m.newpoly)
      undo=quarkx.action()
      undo.exchange(o, dup)
      editor.ok(undo, "make column")

  disable = (len(o.subitems)!=6)

  newpoly = perspectiveRename(o, view)
  list = []

  def finishitem(item, disable=disable, o=o, view=view, newpoly=newpoly):
      disablehint = "This item is disabled because the brush doesn't have 6 faces."
      if disable:
          item.state=qmenu.disabled
          try:
              item.hint=item.hint + "\n\n" + disablehint
          except (AttributeError):
              item.hint="|" + disablehint
      else:
          item.o=o
          item.newpoly = newpoly
          item.view = view

  for (menname, mapname, inv) in (("&Arch", "arch",  1), ("&Cap", "cap", 0)):
    item = qmenu.item(menname, makecap)
    item.inverse = inv
    item.mapname = mapname
    finishitem(item)
    list.append(item)

  cornerhint = """|Makes a smooth curve from the %s side of the brush to the back.

The texture is taken from the back wall, and sized across the curve (compressed a bit) to align with this texture as wrapped onto the %s wall.

To make a rounded corner, put a brush into a corner, project the texture of one of the room walls onto the paralell & `kissing' face of the brush, arrange the camera/view so you're looking square on at the brush and this face is the back one, then and RMB|Curves|right/left corner depending on whether the room-wall you're next to is to the right or the left.

If the textures on the two adjoining walls of the room are properly aligned, the texture on the curve will be too (compressed a bit, but not so as to make much of a difference).
"""


  for (name, left, hint) in (("&Left corner", 1, cornerhint%("left","left")),
                       ("&Right corner", 0, cornerhint%("right","right"))):
    item = qmenu.item(name, makebevel)
    item.inverse = 1
    item.left = left
    item.hint = hint
    finishitem(item)
    list.append(item)

  colitem = qmenu.item("C&olumn", makecolumn, "Make a column")
  finishitem(colitem)
  list.append(colitem)

  curvehint = """|Commands for making curves out of brushes.

The brush must be roughly a box, with the usual six sides.  The curve is implemented as a `duplicator' containing the brush, which determines the overall shape of the brush.

To resize the curve, select the brush in the treeview, and manipulate the sides in the usual manner (when the brush itself is selected, the curve becomes invisible).

When the duplicator is selected, the entity page provides a variety of specifics that can be manipulated to convert from an `arch' to a `cap' (by unchecking 'inverse'), and much else besides.

The curve will be oriented w.r.t. the map view you RMB-clicked on, or, if you're RMB-ing on the treeview, the most recent mapview you clicked in.

If the brush vanishes without being replaced by a shape, the brush may have been too screwy a shape, or looked at from a bad angle. (My attempts to detect these conditions in advance are meeting with unexpected resistance. There is also a bug in that if you apply this to a brush after first opening the map editor, without inserting anything first, the orientations are wrong.)
"""
  curvepop = qmenu.popup("Bezier Curves",list, hint=curvehint)
  if newpoly is None:
    if len(o.subitems)!=6:
      morehint= "\n\nThis item is disabled because the poly doesn't have exactly 6 faces."
    else:
      morehint="\n\nThis item is disabled because I can't figure out which face is front, back, etc.  Make it more box-like, and look at it more head-on in the view."
    curvepop.hint = curvepop.hint+morehint
    curvepop.state = qmenu.disabled
  return curvepop

#
# First new menus are defined, then swapped in for the old ones.
#  `im_func' returns from a method a function that can be
#   assigned as a value.
#
def newpolymenu(o, editor, oldmenu=quarkpy.mapentities.PolyhedronType.menu.im_func):
    "the new right-mouse perspective menu for polys"
    #
    # cf FIXME in maphandles.CenterHandle.menu
    #

    beziersupport = quarkx.setupsubset()["BezierPatchSupport"]
    if (beziersupport is not None) and (beziersupport == "1"):
        # only allow the curves-submenu, if the game-mode supports bezierpatches
        try:
            view = editor.layout.clickedview
        except:
            view = None
        return  [curvemenu(o, editor, view)]+oldmenu(o, editor)
    else:
        return  oldmenu(o, editor)

#
# This trick of redefining things in modules you're based
#  on and importing things from is something you couldn't
#  even think about doing in C++...
#
# It's actually warned against in the Python programming books
#  -- can produce hard-to-understand code -- but can do cool
#  stuff.
#
#
quarkpy.mapentities.PolyhedronType.menu = newpolymenu
