"""   QuArK  -  Quake Army Knife

Implementation of the menu commands related to faces
"""
#
# Copyright (C) 1996-99 Armin Rigo
# THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
# FOUND IN FILE "COPYING.TXT"
#

Info = {
   "plug-in":       "Face Menu",
   "desc":          "Various polyhedron face menu commands.",
   "date":          "1 nov 98",
   "author":        "Armin Rigo",
   "author e-mail": "arigo@planetquake.com",
   "quark":         "Version 5.1" }


import math
import quarkx
from quarkpy.maputils import *
import quarkpy.qmenu
import quarkpy.qtoolbar
import quarkpy.qhandles
import quarkpy.mapcommands
import quarkpy.mapentities
import quarkpy.maphandles
import quarkpy.qmacro



def ForceAngle(m):
    editor = mapeditor()
    if editor is None: return
    obj = editor.layout.explorer.uniquesel
    if obj is None: return
    new = None
    if obj.type == ':f':   # face
        normal = quarkpy.qhandles.alignanglevect(obj.normal, SS_MAP)
        new = obj.copy()
        new.distortion(normal, obj.origin)
    elif obj.type == ':b' or obj.type == ':e':
        for spec, cls in quarkpy.mapentities.ListAngleSpecs(obj):
            s = obj[spec]
            if s:
                stov, vtos = cls.map
                try:
                    normal = stov(s)
                except:
                    continue
                normal = quarkpy.qhandles.alignanglevect(normal, SS_MAP)
                new = obj.copy()
                new[spec] = vtos(normal)
                break
    if new is not None:
        undo = quarkx.action()
        undo.exchange(obj, new)
        editor.ok(undo, "force angle")



class Orientation(quarkpy.qmacro.dialogbox):

    endcolor = PURPLE
    size = (300,140)
    dfsep = 0.4
    dlgdef = """
      {
        Style = "9"
        Caption = "Face orientation"
        sep: = {Typ="S" Txt=" "}    // some space
        compass: = {
          Txt=" Compass angle :"
          Typ="EF1"
          SelectMe="1"
          Hint="Direction (on compass) towards which the side points"
        }
        incline: = {
          Txt=" Incline :"
          Typ="EF1"
          Hint="positive: toward up,  negative: toward down"
        }
        sep: = {Typ="S" Txt=" "}    // some space
        sep: = {Typ="S"}    // a separator line
        cancel:py = { }
      }
    """

    def __init__(self, m):
        self.face = None
        src = quarkx.newobj(":")
        quarkpy.qmacro.dialogbox.__init__(self, quarkx.clickform, src,
          cancel = quarkpy.qtoolbar.button(self.close, "close this box", ico_editor, 0, "Close"))
        editor = mapeditor()
        if editor is None: return
        face = editor.layout.explorer.uniquesel
        if (face is None) or (face.type != ':f'): return

        pitch, roll, yaw = quarkpy.qhandles.vec2angles1(face.normal)
        src["compass"] = roll,
        src["incline"] = pitch,
        self.editor = editor
        self.face = face

    def datachange(self, df):
        if self.face is not None:
            roll = self.src["compass"]
            pitch = self.src["incline"]
            if (roll is not None) and (pitch is not None):
                normal = apply(quarkpy.qhandles.angles2vec1, pitch + roll + (0,))
                new = self.face.copy()
                new.distortion(normal, self.face.origin)
                undo = quarkx.action()
                undo.exchange(self.face, new)
                self.editor.ok(undo, "set orientation")
                self.face = new



def deleteside(m):
    editor = mapeditor()
    if editor is None: return
    face = editor.layout.explorer.uniquesel
    if (face is None) or (face.type != ':f'): return
    for poly in face.faceof:
        if poly.type == ':p':
            test = quarkx.newobj("test:p")
            for f in poly.faces:
                if f != face:
                    test.appenditem(f.copy())
            if test.broken:
                if quarkx.msgbox("Without this face, the polyhedron(s) will no longer be closed. You can continue, but the polyhedron(s) will be broken as long as you don't close it again.", MT_WARNING, MB_OK_CANCEL) != MR_OK:
                    return
                break
    undo = quarkx.action()
    undo.exchange(face, None)
    editor.ok(undo, "delete face")


def makecone(m):
    editor = mapeditor()
    if editor is None: return
    face = editor.layout.explorer.uniquesel
    if (face is None) or (face.type != ':f'): return

    poly = face.parent
    if poly.type != ':p':     # can't conify shared faces
        quarkx.msgbox("Cannot build a cone from a shared face.", MT_ERROR, MB_OK)
        return
    normal = face.normal
    d1 = map(lambda f,n1=normal: f.normal*n1, filter(lambda f,f0=face: f!=f0, poly.faces))
    d1.append(0.0)
    d0 = min((max(d1), 0.95))
    d1 = math.sqrt(1-d0*d0)
    n0 = normal*(d0+1.0)

    undo = quarkx.action()
    vertices = face.verticesof(poly)
    v0 = vertices[-1]
    for v1 in vertices:
        n = (n0 + d1*(normal^(v1-v0)).normalized).normalized
        f = face.copy()
        f.distortion(n, v0)
        undo.put(poly, f, face)
        v0 = v1
    undo.exchange(face, None)
    editor.ok(undo, "conify")


warnonce = 1

def swapsides(m):
    editor = mapeditor()
    if editor is None: return
    face = editor.layout.explorer.uniquesel
    if (face is None) or (face.type != ':f'): return

    global warnonce
    if warnonce:
        if quarkx.msgbox("This command will return the face inside out. It is rarely used, and it is likely to break polyhedrons. Try it anyway, and keep in mind that you can always undo everything.",
          MT_WARNING, MB_OK_CANCEL) == MR_CANCEL:
            return
        warnonce = 0

    undo = quarkx.action()
    new = face.copy()
    new.swapsides()
    undo.exchange(face, new)
    undo.ok(editor.Root, "swap sides")



#--- add the new menu items into the "Commands" menu ---

ForceAngle1 = quarkpy.qmenu.item("&Adjust angle", ForceAngle)
Orientation1 = quarkpy.qmenu.item("&Orientation...", Orientation)
DeleteSide1 = quarkpy.qmenu.item("&Delete face", deleteside)
MakeCone1 = quarkpy.qmenu.item("&Cone over face", makecone)
SwapSides1 = quarkpy.qmenu.item("Swap face sides", swapsides)

quarkpy.mapcommands.items.append(quarkpy.qmenu.sep)   # separator
quarkpy.mapcommands.items.append(Orientation1)
quarkpy.mapcommands.items.append(ForceAngle1)
quarkpy.mapcommands.items.append(DeleteSide1)
quarkpy.mapcommands.items.append(MakeCone1)
quarkpy.mapcommands.items.append(SwapSides1)


def newclick(popup, oldclick = quarkpy.mapcommands.onclick):
    editor = mapeditor()
    if editor is None: return
    obj = editor.layout.explorer.uniquesel
    ForceAngle1.state = (obj is None or not (obj.type in (':f', ':e', ':b'))) and qmenu.disabled
    faceonly = (obj is None or (obj.type != ':f')) and qmenu.disabled
    Orientation1.state = faceonly
    DeleteSide1.state = faceonly
    MakeCone1.state = faceonly
    SwapSides1.state = faceonly
    oldclick(popup)

quarkpy.mapcommands.onclick = newclick



#-- add the new menu items into the face pop-up menu --

def newmenu(o, editor, oldmenu = quarkpy.mapentities.FaceType.menu.im_func):
    Orientation1.state = 0
    DeleteSide1.state = 0
    MakeCone1.state = 0
    return oldmenu(o, editor) + [Orientation1, DeleteSide1, MakeCone1]

quarkpy.mapentities.FaceType.menu = newmenu

