# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
# $Id: maimport.py,v 1.12 2005/06/15 19:20:29 mbaas Exp $

import sys
import mayaascii, cmds
import pluginmanager
import freecamera
import quadrics, box, plane, polyhedron, joint, trimesh, trimeshgeom
import glpointlight, glfreespotlight, glfreedistantlight
import mayaspotlight
from geomobject import *
from cgtypes import *
from worldobject import WorldObject
from sl import *
from math import *
            
                

# MAImporter
class MAImporter(mayaascii.DefaultMAReader):
    """MA import.
    """

    _protocols = ["Import"]

    def __init__(self):
        mayaascii.DefaultMAReader.__init__(self)

    # extension
    def extension():
        """Return the file extensions for this format."""
        return ["ma"]
    extension = staticmethod(extension)

    # description
    def description(self):
        """Return a short description for the file dialog."""
        return "Maya ASCII file"
    description = staticmethod(description)

    # importFile
    def importFile(self, filename, parent=None):
        """Import a MA file."""

        self.root_parent = parent

        f = file(filename)
        self.read(f)

    # begin
    def begin(self):
        mayaascii.DefaultMAReader.begin(self)
        # A dict with created WorldObjects (key = name)
        self.worldobjects = {}

    # end
    def end(self):
        mayaascii.DefaultMAReader.end(self)
        
        issued_warnings = {}
        # Process the nodes by calling an appropriate handler method
        # onNode<Type>(node) for each node type...
        for node in self.nodelist:
            s = node.nodetype
            handlername = "onNode%s%s"%(s[0].upper(), s[1:])
            handler = getattr(self, handlername, None)
            if handler!=None:
                handler(node)
            else:
                if node.nodetype not in issued_warnings:
                    print >>sys.stderr, "WARNING: %s nodes are ignored."%node.nodetype
                    issued_warnings[node.nodetype] = 1

    #################################################

    ### High level callbacks:
    
    # Each node callback takes the current Node object as argument.
    # Whenever a new WorldObject is created, the method has to call
    # the newWorldObject() method to tell the importer about the new object
    # (so that it is known when someone else uses it as parent).

    def ignoreWarning(self, node):
        """(this is just a dummy callback so that no warning is issued)"""
        pass
    onNodeTransform = ignoreWarning
    onNodePolySphere = ignoreWarning
    onNodePolyCube = ignoreWarning
    onNodePolyPlane = ignoreWarning
    onNodeRigidBody = ignoreWarning    

    def onNodePointLight(self, node):
        """PointLight node.
        """
        args = self.fillWorldObjectArgs(node)
        
        cl = vec3(node.getAttrValue("color", "cl", "float3", 1, vec3(1)))
        intensity = node.getAttrValue("intensity", "in", "float", 1, 1.0)
        de = node.getAttrValue("decayRate", "de", "int", 1, 0)
        if de==0:
            catt = 1.0
            latt = 0.0
            qatt = 0.0
        elif de==1:
            catt = 0.0
            latt = 1.0
            qatt = 0.0
        else:
            catt = 0.0
            latt = 0.0
            qatt = 1.0

        lgt = glpointlight.GLPointLight(diffuse = cl,
                                        intensity = intensity,
                                        constant_attenuation = catt,
                                        linear_attenuation = latt,
                                        quadratic_attenuation = qatt,
                                        enabled = args["visible"],
                                        **args)
        self.newWorldObject(lgt, node.getParentName())

    def onNodeSpotLight(self, node):
        """SpotLight node.
        """
        args = self.fillWorldObjectArgs(node)
        
        cl = vec3(node.getAttrValue("color", "cl", "float3", 1, vec3(1)))
        intensity = node.getAttrValue("intensity", "in", "float", 1, 1.0)
        de = node.getAttrValue("decayRate", "de", "int", 1, 0)
        ca = node.getAttrValue("coneAngle", "ca", "float", 1, 40.0)
        pa = node.getAttrValue("penumbraAngle", "pa", "float", 1, 0.0)
        dro = node.getAttrValue("dropoff", "dro", "float", 1, 0.0)

        dms = node.getAttrValue("useDepthMapShadows", "dms", "bool", 1, False)
        dr = node.getAttrValue("dmapResolution", "dr", "int", 1, 512)
        md = node.getAttrValue("useMidDistDmap", "md", "bool", 1, True)
        af = node.getAttrValue("useDmapAutoFocus", "af", "bool", 1, True)
        df = node.getAttrValue("dmapFocus", "df", "float", 1, 90.0)
        fs = node.getAttrValue("dmapFilterSize", "fs", "int", 1, 1)
        db = node.getAttrValue("dmapBias", "db", "float", 1, 0.001)

        args["transform"] = args["transform"]

        lgt = mayaspotlight.MayaSpotLight(color = cl,
                                          intensity = intensity,
                                          decayRate = de,
                                          coneAngle = ca,
                                          penumbraAngle = pa,
                                          dropoff = dro,
                                          useDepthMapShadows = dms,
                                          dmapResolution = dr,
                                          useMidDistDmap = md,
                                          useDmapAutoFocus = af,
                                          dmapFocus = df,
                                          dmapFilterSize = fs,
                                          dmapBias = db,
                                          enabled = args["visible"],
                                          **args)
        self.newWorldObject(lgt, node.getParentName())

    def onNodeDirectionalLight(self, node):
        """DirectionalLight node.
        """
        args = self.fillWorldObjectArgs(node)
        
        cl = vec3(node.getAttrValue("color", "cl", "float3", 1, vec3(1)))
        intensity = node.getAttrValue("intensity", "in", "float", 1, 1.0)

        args["transform"] = args["transform"]*mat4(1).rotation(pi, vec3(1,0,0))

        lgt = glfreedistantlight.GLFreeDistantLight(diffuse = cl,
                                        intensity = intensity,
                                        enabled = args["visible"],
                                        **args)
        self.newWorldObject(lgt, node.getParentName())

    def onNodeJoint(self, node):
        """Joint node.
        """
        args = self.fillWorldObjectArgs(node)

        # Compute transform
        t = vec3(node.getAttrValue("translate", "t", "double3", 1, vec3(0)))
        r = vec3(node.getAttrValue("rotate", "r", "double3", 1, vec3(0)))
        s = vec3(node.getAttrValue("scale", "s", "double3", 1, vec3(1)))
        ra = vec3(node.getAttrValue("rotateAxis", "ra", "double3", 1, vec3(0)))
        roi = node.getAttrValue("rotationInterpolation", "roi", "int", 1, 1)
        ro = node.getAttrValue("rotateOrder", "ro", "int", 1, default=0)
        jo = vec3(node.getAttrValue("jointOrient", "jo", "double3", 1, vec3(0)))
        is_ = vec3(node.getAttrValue("inverseScale", "is", "double3", 1, vec3(1)))
        jot = node.getAttrValue("jointOrientType", "jot", "string", 1, default="xyz")

        S = mat4().scaling(s)
        T = mat4(1,0,0,0, 0,1,0,0, 0,0,1,0, t.x,t.y,t.z,1)
        IS = mat4().scaling(is_)

        sx = sin(radians(ra.x))
        cx = cos(radians(ra.x))
        sy = sin(radians(ra.y))
        cy = cos(radians(ra.y))
        sz = sin(radians(ra.z))
        cz = cos(radians(ra.z))
        AX = mat4(1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1)
        AY = mat4(cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1)
        AZ = mat4(cz,sz,0,0, -sz,cz,0,0, 0,0,1,0, 0,0,0,1)
        RA = AX*AY*AZ

        # Euler-angle rotation (todo: quat)
        sx = sin(radians(r.x))
        cx = cos(radians(r.x))
        sy = sin(radians(r.y))
        cy = cos(radians(r.y))
        sz = sin(radians(r.z))
        cz = cos(radians(r.z))
        RX = mat4(1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1)
        RY = mat4(cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1)
        RZ = mat4(cz,sz,0,0, -sz,cz,0,0, 0,0,1,0, 0,0,0,1)
        a,b,c = ["XYZ", "YZX", "ZXY", "XZY", "YXZ", "ZYX"][ro]
        exec "R=R%s*R%s*R%s"%(a,b,c)

        sx = sin(radians(jo.x))
        cx = cos(radians(jo.x))
        sy = sin(radians(jo.y))
        cy = cos(radians(jo.y))
        sz = sin(radians(jo.z))
        cz = cos(radians(jo.z))
        AX = mat4(1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1)
        AY = mat4(cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1)
        AZ = mat4(cz,sz,0,0, -sz,cz,0,0, 0,0,1,0, 0,0,0,1)
        a,b,c = jot.upper()
        exec "JO=A%s*A%s*A%s"%(a,b,c)

        WT = S*RA*R*IS*T
           
#        WT = S*RA*R*IS*T
#        WT = T
        WT = WT.transpose()
        args["transform"] = WT

        jnt = joint.Joint(radius = 0.5*node.getAttrValue("radius", "radi", "float", 1, 1.0),
                          offsetTransform = JO.inverse(),
                          **args)
        jnt.freezePivot()
        # Reset offset transform (so that pivot frame and local frame coincide)
        jnt.setOffsetTransform(mat4(1))
        self.newWorldObject(jnt, node.getName())


    def onNodeCamera(self, node):
        """Camera node.
        """
        args = self.fillWorldObjectArgs(node)
        self.getCameraArgs(node, args)
        args["transform"] = args["transform"]*mat4(1).rotation(pi, vec3(0,1,0))
        cam = freecamera.FreeCamera(**args)
        self.newWorldObject(cam, node.getParentName())

    def onNodeMesh(self, node):
        args = self.fillWorldObjectArgs(node)
        parentnode = node.getParent()
        
        # Check if the mesh is just a regular mesh or if it's created by
        # a "creator" node (cube, sphere, ...). 
        n,a = node.getInNode("inMesh", "i")
        if n==None:
            meshtype = None
            verts = node.getAttrValue("vrts", "vt", "float3", None, [])
            edges = node.getAttrValue("edge", "ed", "long3", None, [])
            faces = node.getAttrValue("face", "fc", "polyFaces", None, [])
            polyobj = polyhedron.Polyhedron(verts=verts, **args)
            geom = polyobj.geom
            # Initialize the polygons...
            geom.setNumPolys(len(faces))
            valid_st = True
            for i, face in enumerate(faces):
                valid_st &= face.hasValidTexCoords()
                poly = []
                # Iterate over all "loop" (i.e. the outer loop and the holes)
                # and create the vertex id list for each loop
                for loop in [face.f]+face.h:
                    p = []
                    for e in loop:
                        if e>=0:
                            p.append(edges[e][0])
                        else:
                            p.append(edges[-e-1][1])
                    poly.append(p)
                geom.setPoly(i, poly)

            # Set texture coordinates...
            uvset = node.getAttrValue("uvSet", "uvst", type=None, n=None)
            if uvset!=None:
                stlst = list(uvset[0].uvsp)
                if valid_st and len(stlst)>0:
                    geom.newVariable("st", USER, FLOAT, 2, len(stlst))
                    st = geom.slot("st")
                    for i,stval in enumerate(stlst):
                        st[i] = stval
                    # Set the tex coord faces...
                    geom.newVariable("stfaces", FACEVARYING, INT)
                    stfaces = geom.slot("stfaces")
                    i = 0
                    for face in faces:
                        for mulst in face.mu:
                            for id in mulst[0][1]:
                                stfaces[i] = id
                                i+=1

            # Set colors...
            colattr = node.getAttrValue("colors", "clr", type="float4", n=None)
            if colattr!=None:
                collst = list(colattr)
                geom.newVariable("Cs", USER, COLOR, 1, len(collst))
                Cs = geom.slot("Cs")
                for i,Csval in enumerate(collst):
                    Cs[i] = vec3(Csval[:3])
                # Set the color faces...
                geom.newVariable("Csfaces", FACEVARYING, INT)
                Csfaces = geom.slot("Csfaces")
                i = 0
                for face in faces:
                    for fclst in face.fc:
                        for id in fclst:
                            Csfaces[i] = id
                            i+=1

            # Apply vertex tweaks...
            pnts = node.getAttrValue("pnts", "pt", "float3", None, [])
            pnts = list(pnts)
            for i,v in enumerate(geom.verts):
                if i>=len(pnts):
                    break
                p = pnts[i]
                if p!=None:
                    if type(p)==list:
                        p = p[0]
                    v += vec3(p)
                    geom.verts[i] = v

            # Convert the poly to a trimesh if it's a rigid body
            self.getRigidBodyArgs(parentnode, args)
            if args["dynamics"]:
                tm = trimeshgeom.TriMeshGeom()
                polyobj.geom.convert(tm)
                cmds.delete(polyobj)
                polyobj = trimesh.TriMesh(**args)
                polyobj.geom = tm

            self.newWorldObject(polyobj, node.getParentName())
        else:
            creator = self.findNode(n)
            meshtype = creator.nodetype
            # Sphere?
            if meshtype=="polySphere":
                self.createSphere(node, creator, args)
            # Cube?
            elif meshtype=="polyCube":
                self.createBox(node, creator, args)
            # Plane?
            elif meshtype=="polyPlane":
                self.createPlane(node, creator, args)

    def createSphere(self, mesh, creator, args):
        """Creates a true sphere from a polySphere node.

        mesh is the mesh node that takes its input from creator.
        creator is an import Node which must be of type polySphere.
        args is a dictionary that already contains the basic WorldObject
        arguments that can be passed to the constructor.
        """
        parentnode = mesh.getParent()
        nod,attr = parentnode.getOutAttr("worldMatrix", "wm", "rigidBody")
        dynamics = (nod!=None)
        r = creator.getAttrValue("radius", "r", "float", 1, 1.0)
        segsu = creator.getAttrValue("subdivisionAxis", "sa", "int", 1, 20)
        segsv = creator.getAttrValue("subdivisionHeight", "sh", "int", 1, 20)
        ax = vec3(creator.getAttrValue("axis", "ax", "double3", 1, vec3(0,1,0)))
#        args["transform"] *= self.axisToTransform(ax)
#        if ax==vec3(1,0,0):
#            args["transform"] *= mat4(1).rotation(pi/4, vec3(0,0,1))
        self.getRigidBodyArgs(parentnode, args)
        s = quadrics.Sphere(radius=r,
                            segmentsu=segsu, segmentsv=segsv,
                            **args)
        self.newWorldObject(s, parentnode.getName())

    def createBox(self, mesh, creator, args):
        """Creates a true box from a polyCube node.

        mesh is the mesh node that takes its input from creator.
        creator is an import Node which must be of type polyCube.
        args is a dictionary that already contains the basic WorldObject
        arguments that can be passed to the constructor.
        """
        parentnode = mesh.getParent()
        w = creator.getAttrValue("width", "w", "float", 1, 1.0)
        h = creator.getAttrValue("height", "h", "float", 1, 1.0)
        d = creator.getAttrValue("depth", "d", "float", 1, 1.0)
        sw = creator.getAttrValue("subdivisionWidth", "sw", "int", 1, 1)
        sh = creator.getAttrValue("subdivisionHeight", "sh", "int", 1, 1)
        sd = creator.getAttrValue("subdivisionDepth", "sd", "int", 1, 1)
        ax = vec3(creator.getAttrValue("axis", "ax", "double3", 1, vec3(0,1,0)))

        # "Rename" width/height/depth to match the axis setting...
        if ax==vec3(1,0,0):
            tmp = d
            d = w
            w = h
            h = tmp
            tmp = sd
            sd = sw
            sw = sh
            sh = tmp
        elif ax==vec3(0,1,0):
            tmp = d
            d = h
            h = tmp
            tmp = sd
            sd = sh
            sh = tmp
            
        self.getRigidBodyArgs(parentnode, args)
        b = box.Box(lx=w, ly=d, lz=h,
                    segmentsx=sw, segmentsy=sd, segmentsz=sh,
                    **args)
        self.newWorldObject(b, parentnode.getName())

    def createPlane(self, mesh, creator, args):
        """Creates a true plane from a polyPlane node.

        mesh is the mesh node that takes its input from creator.
        creator is an import Node which must be of type polyPlane.
        args is a dictionary that already contains the basic WorldObject
        arguments that can be passed to the constructor.
        """
        parentnode = mesh.getParent()
        w = creator.getAttrValue("width", "w", "float", 1, 1.0)
        h = creator.getAttrValue("height", "h", "float", 1, 1.0)
        sw = creator.getAttrValue("subdivisionWidth", "sw", "int", 1, 1)
        sh = creator.getAttrValue("subdivisionHeight", "sh", "int", 1, 1)
        ax = vec3(creator.getAttrValue("axis", "ax", "double3", 1, vec3(0,1,0)))

        if ax!=vec3(0,0,1):
            print "WARNING: Plane %s ignored because axis!=z"%node.getName()
            return

#        args["transform"] *= self.axisToTransform(ax)
        self.getRigidBodyArgs(parentnode, args)
        if "static" in args:
            del args["static"]
        p = plane.Plane(lx=w, ly=h,
                segmentsx=sw, segmentsy=sh,
                **args)
        self.newWorldObject(p, parentnode.getName())


    def getRigidBodyArgs(self, transform, res):
        """Retrieve rigid body information.

        transform is the transform node as NodeData object.
        """
        rbnode,attr = transform.getOutAttr("worldMatrix", "wm", "rigidBody")
        # No rigid body node? then don't add any arguments
        if rbnode==None:
            res["dynamics"] = False
            return res

        res["dynamics"] = True
        res["mass"] = rbnode.getAttrValue("mass", "mas", "float", 1, 1.0)
        res["static"] = not rbnode.getAttrValue("active", "act", "bool", 1, True)
        return res
        

#    def axisToTransform(self, axis):
#        if axis==vec3(1,0,0):
#            return mat4(1).rotation(pi/2, vec3(0,1,0))
#        elif axis==vec3(0,1,0):
#            return mat4(1).rotation(pi/2, vec3(1,0,0))
#        else:
#            return mat4(1)

    ### Helpers:

    def newWorldObject(self, obj, mayanodename):
        """Notify about the creation of a new world object.

        This method has to be called whenever a new WorldObject is created.
        This is needed to later find the object when it is used as parent.
        """
        if mayanodename in self.worldobjects:
            print "WARNING: Duplicate node name '%s'"%mayanodename
            
        self.worldobjects[mayanodename] = obj

    # getCameraArgs
    def getCameraArgs(self, node, res):
        """Create the argument dict for the WorldObject constructor.

        node must contain the data from a Maya transform node. 
        """

        fl = node.getAttrValue("focalLength", "fl", "float", 1, 35.0)
        res["focallength"] = fl
        cap = node.getAttrValue("cameraAperture", "cap", "double2", 1, (1.41732, 0.94488))

        fov = degrees(2*atan((cap[0]*25.4)/(2*fl)))
        # Convert the FOV from horizontal to vertical direction
        # (Todo: What aspect ratio to use?)
        fov = degrees(atan(480/640.0*tan(radians(fov/2.0))))*2.0
        res["fov"] = fov
        res["fstop"] = node.getAttrValue("fStop", "fs", "float", 1, 5.6)
        return res

    # fillWorldObjectArgs
    def fillWorldObjectArgs(self, node, args=None):
        """Fill the argument dict for the WorldObject constructor.

        node must be a Node object containing either the transform node
        or the shape node (whose parent must then be a transform node).
        args must be a dictionary which is filled with the arguments
        for a WorldObject constructor. It can also be None in which case
        a new dictionary is created and returned.
        The following attributes are written into args:

        - name: The name of the transform node
        - parent: The parent WorldObject (or self.root_parent)
        - transform: The transform matrix (pos rot may come from a rigidBody node)

        The return value is the args dictionary or the newly created dict.
        """
        # Obtain the transform node -> tnode
        if node.nodetype in ["transform", "joint"]:
            tnode = node
        else:
            tnode = node.getParent()
            if tnode==None or tnode.nodetype!="transform":
                raise ValueError, "transform node not found for node %s."%node.getFullName()

        if args==None:
            args = {}

        # Name
        args["name"] = tnode.getName()

        # Parent
        parentnode = tnode.getParent()
        if parentnode!=None:
            parentname = parentnode.getFullName()
            if parentname!=None:
                parent = self.worldobjects.get(parentname, None)
                if parent==None:
                    print "WARNING: Parent '%s' not found."%parentname
                    parent = self.root_parent
        else:
            parent = self.root_parent
        args["parent"] = parent

        # Check if the world matrix is connected to a rigid body.
        # In this case, the rigid body stores the initial position and
        # orientation
        rbnode,attr = tnode.getOutAttr("worldMatrix", "wm", "rigidBody")

        # Compute transform
        if rbnode==None:
            t = vec3(tnode.getAttrValue("translate", "t", "double3", 1, vec3(0)))
            r = vec3(tnode.getAttrValue("rotate", "r", "double3", 1, vec3(0)))
        else:
            t = vec3(rbnode.getAttrValue("initialPosition", "ip", "double3", 1, vec3(0)))
            r = vec3(rbnode.getAttrValue("initialOrientation", "ior", "double3", 1, vec3(0)))
        s = vec3(tnode.getAttrValue("scale", "s", "double3", 1, vec3(1)))
        sp = vec3(tnode.getAttrValue("scalePivot", "sp", "double3", 1, vec3(0)))
        spt = vec3(tnode.getAttrValue("scalePivotTranslate", "spt", "double3", 1, vec3(0)))
        sh = vec3(tnode.getAttrValue("shear", "sh", "double3", 1, vec3(0)))
        rp = vec3(tnode.getAttrValue("rotatePivot", "rp", "double3", 1, vec3(0)))
        rpt = vec3(tnode.getAttrValue("rotatePivotTranslate", "rpt", "double3", 1, vec3(0)))
        ra = vec3(tnode.getAttrValue("rotateAxis", "ra", "double3", 1, vec3(0)))
        roi = tnode.getAttrValue("rotationInterpolation", "roi", "int", 1, 1)
        ro = tnode.getAttrValue("rotateOrder", "ro", "int", 1, default=0)

        SP = mat4(1,0,0,0, 0,1,0,0, 0,0,1,0, sp.x,sp.y,sp.z,1)
        ST = mat4(1,0,0,0, 0,1,0,0, 0,0,1,0, spt.x,spt.y,spt.z,1)
        S = mat4().scaling(s)
        SH = mat4(1,0,0,0, sh.x,1,0,0, sh.y,sh.z,1,0, 0,0,0,1)
        RP = mat4(1,0,0,0, 0,1,0,0, 0,0,1,0, rp.x,rp.y,rp.z,1)
        RT = mat4(1,0,0,0, 0,1,0,0, 0,0,1,0, rpt.x,rpt.y,rpt.z,1)
        T = mat4(1,0,0,0, 0,1,0,0, 0,0,1,0, t.x,t.y,t.z,1)

        sx = sin(radians(ra.x))
        cx = cos(radians(ra.x))
        sy = sin(radians(ra.y))
        cy = cos(radians(ra.y))
        sz = sin(radians(ra.z))
        cz = cos(radians(ra.z))
        AX = mat4(1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1)
        AY = mat4(cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1)
        AZ = mat4(cz,sz,0,0, -sz,cz,0,0, 0,0,1,0, 0,0,0,1)
        RA = AX*AY*AZ

        # Euler-angle rotation (todo: quat)
        sx = sin(radians(r.x))
        cx = cos(radians(r.x))
        sy = sin(radians(r.y))
        cy = cos(radians(r.y))
        sz = sin(radians(r.z))
        cz = cos(radians(r.z))
        RX = mat4(1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1)
        RY = mat4(cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1)
        RZ = mat4(cz,sz,0,0, -sz,cz,0,0, 0,0,1,0, 0,0,0,1)
        a,b,c = ["XYZ", "YZX", "ZXY", "XZY", "YXZ", "ZYX"][ro]
        exec "R=R%s*R%s*R%s"%(a,b,c)

        WT = SP.inverse()*S*SH*SP*ST*RP.inverse()*RA*R*RP*RT*T
        WT = WT.transpose()
        args["transform"] = WT

        # Visibility
        args["visible"] = tnode.getAttrValue("visibility", "v", "bool", 1, True)
        
        return args


######################################################################

# Register the importer class as a plugin class
pluginmanager.register(MAImporter)
