home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / cmds.py < prev    next >
Encoding:
Python Source  |  2007-01-11  |  31.4 KB  |  1,061 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35. # $Id: cmds.py,v 1.12 2006/02/16 17:49:38 mbaas Exp $
  36.  
  37. ## \file cmds.py
  38. ## Contains builtin and user defined commands.
  39.  
  40. # (this line separates the \file comment from the module docs)
  41.  
  42. """Commands.
  43. """
  44.  
  45. import sys, os, os.path, types, string
  46. import pluginmanager
  47. import eventmanager
  48. import events
  49. from scene import getScene
  50. import worldobject
  51. import draw, drawtextgeom
  52. import trimeshgeom
  53. import polyhedrongeom
  54. import group as group_module
  55. import _core
  56. from cgtypes import *
  57.  
  58. class NoImporter(Exception): pass
  59. class NoExporter(Exception): pass
  60.  
  61.  
  62.  
  63. # TriMeshPolyAdapter
  64. class TriMeshPolyAdapter:
  65.     """Provides the PolyhedronGeom interface for a TriMeshGeom.
  66.  
  67.     This is a preliminary class that's used in the convFaceVarToVar()
  68.     function so that TriMeshGeoms can be handled the same way than
  69.     PolyhedronGeoms.
  70.  
  71.     Todo: This class should be stored somewhere else and probably be made
  72.     available as a true adapter that can be used with the protocols framework.
  73.  
  74.     Todo: Most Component methods are still missing.
  75.     """
  76.     def __init__(self, tm):
  77.         self.tm = tm
  78.         
  79.         self.verts = tm.verts
  80.         self.verts_slot = tm.verts_slot
  81.  
  82.     def slot(self, name):
  83.         return self.tm.slot(name)
  84.  
  85.     def boundingBox(self):
  86.         return self.tm.boundingBox()
  87.  
  88.     def drawGL(self):
  89.         self.tm.drawGL()
  90.  
  91.     def uniformCount(self):
  92.         return self.tm.uniformCount()
  93.  
  94.     def vertexCount(self):
  95.         return self.tm.vertexCount()
  96.  
  97.     def varyingCount(self):
  98.         return self.tm.varyingCount()
  99.  
  100.     def faceVaryingCount(self):
  101.         return self.tm.faceVaryingCount()
  102.  
  103.     def faceVertexCount(self):
  104.         return self.tm.faceVertexCount()
  105.  
  106.     def newVariable(self, name, storage, type, multiplicity=1, user_n=0):
  107.         self.tm.newVariable(name, storage, type, multiplicity, user_n)
  108.  
  109.     def deleteVariable(self, name):
  110.         self.tm.deleteVariable(name)
  111.  
  112.     def deleteAllVariables(self):
  113.         self.tm.deleteAllVariables()
  114.  
  115.     def findVariable(self, name):
  116.         return self.tm.findVariable(name)
  117.  
  118.     def iterVariables(self):
  119.         return self.tm.iterVariables()
  120.  
  121.     def convert(self, targetgeom):
  122.         self.tm.convert(targetgeom)
  123.         
  124.     def hasPolysWithHoles(self):
  125.         return False
  126.  
  127.     def getNumPolys(self):
  128.         return self.tm.faces.size()
  129.  
  130.     def getNumLoops(self, poly):
  131.         return 1
  132.  
  133.     def getNumVerts(self, poly, loop):
  134.         return 3
  135.  
  136.     def setNumPolys(self, num):
  137.         self.tm.faces.resize(num)
  138.  
  139.     def setNumLoops(self, poly, num):
  140.         if num>1:
  141.             raise RuntimeError, "Triangles cannot have holes"
  142.  
  143.     def getLoop(self, poly, loop):
  144.         if loop==0:
  145.             return self.tm.faces[poly]
  146.         else:
  147.             raise RuntimeError, "Triangles have no holes"
  148.  
  149.     def setLoop(self, poly, loop, vloop):
  150.         if loop==0:
  151.             self.tm.faces[poly] = vloop
  152.         else:
  153.             raise RuntimeError, "Triangles cannot have holes"
  154.  
  155.     def getPoly(self, poly):
  156.         return [self.tm.faces[poly]]
  157.  
  158.     def setPoly(self, poly, polydef):
  159.         self.tm.faces[poly] = polydef[0]
  160.     
  161.  
  162. def convFaceVarToVar(geom):
  163.     """Convert all facevarying into varying.
  164.  
  165.     All facevarying variables are converted into varying variables and
  166.     all facevertex variables into vertex variables.
  167.     This is done by splitting the vertices in the mesh.
  168.     The number of values (and the values themselves) of the facevarying
  169.     variables do not change, only the verts and varying/vertex variables
  170.     are modified.
  171.  
  172.     geom can be either a TriMeshGeom or a PolyhedronGeom.
  173.  
  174.     The return value is the new geom object (the original object is not
  175.     modified).
  176.     """
  177.  
  178.     if isinstance(geom, trimeshgeom.TriMeshGeom):
  179.         newgeom = trimeshgeom.TriMeshGeom()
  180.         newgeom.faces.resize(geom.faces.size())
  181.         geom.faces.copyValues(0, geom.faces.size(), newgeom.faces, 0)
  182.         workinggeom = TriMeshPolyAdapter(newgeom)
  183.     elif isinstance(geom, polyhedrongeom.PolyhedronGeom):
  184.         newgeom = polyhedrongeom.PolyhedronGeom()
  185.         newgeom.setNumPolys(geom.getNumPolys())
  186.         workinggeom = newgeom
  187.         # Copy the polygons (even though the indices have to be modified
  188.         # afterwards), so that the variables can be created
  189.         for i in range(geom.getNumPolys()):
  190.             poly = geom.getPoly(i)
  191.             newgeom.setPoly(i, poly)
  192.     else:
  193.         raise TypeError, "geom must be a TriMeshGeom or PolyhedronGeom"
  194.  
  195.     # Allocate storage for the vertices
  196.     workinggeom.verts.resize(geom.faceVaryingCount())
  197.  
  198.     # This is a list with (oldslot,newslot) tuples. This is later used to
  199.     # initialize the new varying/vertex slots.
  200.     varyingvars = [(geom.verts, newgeom.verts)]
  201.  
  202.     # Create a copy of all variables...
  203.     for name,storage,type,mult in geom.iterVariables():
  204.  
  205.         # facevarying/facevertex will be converted to varying/vertex
  206.         if storage==_core.VarStorage.FACEVARYING:
  207.             newstorage = _core.VarStorage.VARYING
  208.         elif storage==_core.VarStorage.FACEVERTEX:
  209.             newstorage = _core.VarStorage.VERTEX
  210.         else:
  211.             newstorage = storage            
  212.             
  213.         slot = geom.slot(name)
  214.         workinggeom.newVariable(name, newstorage, type, mult, slot.size())
  215.         newslot = workinggeom.slot(name)
  216.         # Original varying/vertex variables will be initialized later,
  217.         # everything else is just a copy.
  218.         if storage==_core.VarStorage.VARYING or storage==_core.VarStorage.VERTEX:
  219.             varyingvars.append((slot, newslot))
  220.         else:
  221.             slot.copyValues(0, slot.size(), newslot, 0)
  222.  
  223.  
  224.     # Initialize the varying/vertex variables and update the vertex indices
  225.     # in the faces...
  226.     vidx = 0
  227.     for i in range(workinggeom.getNumPolys()):
  228.         poly = workinggeom.getPoly(i)
  229.         # ids: Flattened version of poly (i.e. all vertex ids)
  230.         ids = []
  231.         # newpoly: New polygon with new indices (sequentially numbered)
  232.         newpoly = []
  233.         vidx2 = vidx
  234.         for loop in poly:
  235.             ids += loop
  236.             newpoly.append(range(vidx2, vidx2+len(loop)))
  237.             vidx2 += len(loop)
  238.  
  239.         workinggeom.setPoly(i, newpoly)
  240.  
  241.         for id in ids:
  242.             for slot,newslot in varyingvars:
  243.                 newslot[vidx] = slot[id]
  244.             vidx += 1
  245.  
  246.     return newgeom
  247.         
  248.  
  249. def removeIsolatedVertices(tm):
  250.  
  251.     print >>sys.stderr, 'Removing isolated vertices...'
  252.  
  253.     newgeom = trimeshgeom.TriMeshGeom()
  254.     newgeom.faces.resize(len(tm.faces))
  255.  
  256.     # Key: Old vertex index   Value: New vertex index
  257.     vert_used = {}
  258.     # Index: New id   Value: Old id
  259.     newverts = []
  260.     for i in range(len(tm.faces)):
  261.         ai,bi,ci = tm.faces[i]
  262.         if ai not in vert_used:
  263.             vert_used[ai] = len(newverts)
  264.             newverts.append(ai)
  265.         if bi not in vert_used:
  266.             vert_used[bi] = len(newverts)
  267.             newverts.append(bi)
  268.         if ci not in vert_used:
  269.             vert_used[ci] = len(newverts)
  270.             newverts.append(ci)
  271.  
  272.         newgeom.faces[i] = (vert_used[ai], vert_used[bi], vert_used[ci])
  273.             
  274.     newgeom.verts.resize(len(newverts))
  275.  
  276.     # Set new vertices
  277.     for i in range(len(newverts)):
  278.         newgeom.verts[i] = tm.verts[newverts[i]]
  279.  
  280.     # Copy remaining variables...
  281.     for name,storage,type,mult in tm.iterVariables():
  282.         if storage==_core.VarStorage.VARYING:
  283.             newgeom.newVariable(name, _core.VarStorage.VARYING, type, mult)
  284.             src = tm.slot(name)
  285.             dst = newgeom.slot(name)
  286.             for i in range(len(newverts)):
  287.                 oldid = newverts[i]
  288.                 dst[i] = src[oldid]
  289.         else:
  290.             src = tm.slot(name)
  291.             # Create slot
  292.             if storage==_core.VarStorage.USER:
  293.                 newgeom.newVariable(name, _core.VarStorage.USER, type, mult, len(src))
  294.             else:
  295.                 newgeom.newVariable(name, storage, type, mult)
  296.             dst = newgeom.slot(name)
  297.             # Copy slot
  298.             src.copyValues(0, len(src), dst, 0)
  299. #            for i in range(len(src)):
  300. #                dst[i] = src[i]
  301.  
  302.     return newgeom
  303.     
  304.  
  305. # extractUniform
  306. def extractUniform(tm, varname, value):
  307.     """Extract a uniform variable from a TriMesh.
  308.  
  309.     The resulting mesh will only contain faces that have the
  310.     given value.
  311.     """
  312.  
  313.     print >>sys.stderr, 'Extracting variable "%s"...'%(varname)
  314.  
  315.     var_slot = tm.slot(varname)
  316.     # A list representing the new faces.
  317.     # Each entry newfaces[i] represents face i of the new mesh
  318.     # The value newfaces[i] is the index of the old face
  319.     newfaces = []
  320.  
  321.     # Determine the new faces...
  322.     for i in range(len(tm.faces)):
  323.         if var_slot[i]==value:
  324.             newfaces.append(i)
  325.  
  326.     newgeom = trimeshgeom.TriMeshGeom()
  327.     newgeom.faces.resize(len(newfaces))
  328.     newgeom.verts.resize(len(tm.verts))
  329.  
  330.     # Initialize new faces...
  331.     for i in range(len(newfaces)):
  332.         oldid = newfaces[i]
  333.         newgeom.faces[i] = tm.faces[oldid]
  334.  
  335.     # Copy vertices...
  336.     tm.verts.copyValues(0, len(tm.verts), newgeom.verts, 0)
  337. #    for i in range(len(tm.verts)):
  338. #        newgeom.verts[i] = tm.verts[i]
  339.  
  340.     # Copy remaining variables...
  341.     for name,storage,type,mult in tm.iterVariables():
  342.         if storage==_core.VarStorage.UNIFORM:
  343.             newgeom.newVariable(name, _core.VarStorage.UNIFORM, type, mult)
  344.             src = tm.slot(name)
  345.             dst = newgeom.slot(name)
  346.             for i in range(len(newfaces)):
  347.                 oldid = newfaces[i]
  348.                 dst[i] = src[oldid]
  349.         elif (storage==_core.VarStorage.FACEVARYING or
  350.               storage==_core.VarStorage.FACEVERTEX):
  351.             newgeom.newVariable(name, storage, type, mult)
  352.             src = tm.slot(name)
  353.             dst = newgeom.slot(name)
  354.             for i in range(len(newfaces)):
  355.                 oldid = newfaces[i]
  356.                 dst[3*i] = src[3*oldid]
  357.                 dst[3*i+1] = src[3*oldid+1]
  358.                 dst[3*i+2] = src[3*oldid+2]
  359.         else:
  360.             src = tm.slot(name)
  361.             # Create slot
  362.             if storage==_core.VarStorage.USER:
  363.                 newgeom.newVariable(name, _core.VarStorage.USER, type, mult, len(src))
  364.             else:
  365.                 newgeom.newVariable(name, storage, type, mult)
  366.             dst = newgeom.slot(name)
  367.             # Copy slot
  368.             src.copyValues(0, len(src), dst, 0)
  369. #            for i in range(len(src)):
  370. #                dst[i] = src[i]
  371.  
  372.     return newgeom
  373.  
  374.  
  375. def _mapID(id, attrid, attr, attr_lut, newverts):
  376.     """Determine new vertex ID.
  377.  
  378.     attr_lut is a dictionary that servers as attribute lookup table.
  379.     Key is the vertex ID and the value is another dictionary that
  380.     has the attribute ID as key and the new ID as value.
  381.  
  382.     \param id  Old vertex ID
  383.     \param attrid  Attribute ID
  384.     \param attr  Attribute value
  385.     \param attr_lut  Attribute lookup table
  386.     \partam newverts
  387.     \return New vertex ID
  388.     """
  389.     if id not in attr_lut:
  390.         attr_lut[id] = {}
  391.         
  392.     newid_lut = attr_lut[id]
  393.     if attrid in newid_lut:
  394.         return newid_lut[attrid]
  395.     else:
  396.         newid = len(newverts)
  397.         newid_lut[attrid] = newid
  398.         newverts.append((id, attr))
  399.         return newid
  400.  
  401.  
  402. def convMeshAttr(tm, attrname, attrfacesname=None):
  403.     """Convert attribute type.
  404.  
  405.     attrname is the name of the attribute that should be turned into
  406.     a "varying" attribute.
  407.     attrfacesnames is the name of the corresponding attribute
  408.     faces. This must be a uniform variable.
  409.  
  410.     The result is a copy of tm where the attribute attrname is a
  411.     "varying" variable. This means each vertex has a unique attribute.
  412.     The result has the same number of faces than tm, but the number
  413.     of vertices is usally higher.
  414.  
  415.     \param tm TriMeshGeom
  416.     \param attrname Attribute 
  417.     \param attrsfacesnames Faces attribute
  418.     \return New TriMeshGeom
  419.     """
  420.     if attrfacesname==None:
  421.         attrfacesname = attrname+"faces"
  422.         
  423.     print >>sys.stderr, 'Converting "%s"/"%s" into "varying %s"...'%(attrname,attrfacesname,attrname)
  424.     newgeom = trimeshgeom.TriMeshGeom()
  425.  
  426.     attrdesc = tm.findVariable(attrname)
  427.  
  428.     attr_slot = tm.slot(attrname)
  429.     attrfaces_slot = tm.slot(attrfacesname)
  430.  
  431.     attr_lut = {}
  432.     # A list of tuples (Old vertex ID, Attribute ID)
  433.     # An entry newverts[i] represents vertex i of the new mesh
  434.     newverts = []
  435.     
  436.     newgeom.faces.resize(len(tm.faces))
  437.     
  438.     # Check each face...
  439.     for i in range(len(tm.faces)):
  440.         ai,bi,ci = tm.faces[i]
  441.         attrai,attrbi,attrci = attrfaces_slot[i]
  442.  
  443.         # Get the 3 attributes associated to the current face
  444.         attra = attr_slot[attrai]
  445.         attrb = attr_slot[attrbi]
  446.         attrc = attr_slot[attrci]
  447.  
  448.         newai = _mapID(ai, attrai, attra, attr_lut, newverts)
  449. #        print "A: %d -> %d"%(ai,newai)
  450.         newbi = _mapID(bi, attrbi, attrb, attr_lut, newverts)
  451. #        print "B: %d -> %d"%(bi,newbi)
  452.         newci = _mapID(ci, attrci, attrc, attr_lut, newverts)
  453. #        print "C: %d -> %d"%(ci,newci)
  454.         
  455.         newgeom.faces[i] = (newai,newbi,newci)
  456.  
  457.     # Set the new vertices...
  458.     numnewverts = len(newverts)
  459.     newgeom.verts.resize(numnewverts)
  460.     newgeom.newVariable(attrname, _core.VarStorage.VARYING, attrdesc[2], attrdesc[3])
  461.     newattr_slot = newgeom.slot(attrname)
  462.     for i in range(numnewverts):
  463.         oldid,attr = newverts[i]
  464.         newgeom.verts[i] = tm.verts[oldid]
  465.         newattr_slot[i] = attr
  466.  
  467.     # Copy remaining variables...
  468.     for name,storage,type,mult in tm.iterVariables():
  469.         if name==attrname or name==attrfacesname:
  470.             continue
  471.         if storage==_core.VarStorage.VARYING:
  472.             newgeom.newVariable(name, _core.VarStorage.VARYING, type, mult)
  473.             src = tm.slot(name)
  474.             dst = newgeom.slot(name)
  475.             for i in range(numnewverts):
  476.                 oldid,attr = newverts[i]
  477.                 dst[i] = src[oldid]
  478.         else:
  479.             src = tm.slot(name)
  480.             # Create slot
  481.             if storage==_core.VarStorage.USER:
  482.                 newgeom.newVariable(name, _core.VarStorage.USER, type, mult, len(src))
  483.             else:
  484.                 newgeom.newVariable(name, storage, type, mult)
  485.             dst = newgeom.slot(name)
  486.             # Copy slot
  487.             src.copyValues(0, len(src), dst, 0)
  488. #            for i in range(len(src)):
  489. #                dst[i] = src[i]
  490.  
  491.     return newgeom
  492.  
  493. # fitCone
  494. def fitCone(pos, obj):
  495.     """Compute a cone that has its apex at pos and that includes obj.
  496.  
  497.     The generated cone is the minimal cone that entirely contains the
  498.     bounding box of obj (which must be a WorldObject).
  499.     pos is the apex of the cone given in world coordinates.
  500.     The return value is a tuple (n,w) where n is the axis direction of the
  501.     cone and w is the (full) angle in radians.
  502.     """
  503.     pos = vec3(pos)
  504.     W = obj.worldtransform
  505.     Winv = W.inverse()
  506.     p = Winv*pos
  507.     
  508.     bb = obj.boundingBox()
  509.     a,b,c,d,e,f,g,h = _bbCorners(bb)
  510.     a = (a-p).normalize()
  511.     b = (b-p).normalize()
  512.     c = (c-p).normalize()
  513.     d = (d-p).normalize()
  514.     e = (e-p).normalize()
  515.     f = (f-p).normalize()
  516.     g = (g-p).normalize()
  517.     h = (h-p).normalize()
  518.     n1,w1 = _cone(a,h)
  519.     n2,w2 = _cone(b,g)
  520.     n3,w3 = _cone(c,f)
  521.     n4,w4 = _cone(d,e)
  522.     w = max(w1,w2,w3,w4)
  523.     if w1==w:
  524.         n = n1
  525.     elif w2==w:
  526.         n = n2
  527.     elif w3==w:
  528.         n = n3
  529.     else:
  530.         n = n4
  531.  
  532.     n = W.getMat3()*n
  533.     return n,w
  534.  
  535. # _cone
  536. def _cone(a,b):
  537.     """Create a cone from two directions.
  538.  
  539.     a and b are two normalized directions that point along the generated
  540.     cone on two opposite directions, i.e. the returned cone is the minmal
  541.     cone that has the 'rays' a and b on its surface.
  542.     The return value is a tuple (n,w) where n is the cone axis and w
  543.     the (full) angle in radians.
  544.  
  545.     This is a helper function for the fitCone() function.
  546.     """
  547.     n = (a+b).normalize()
  548.     w = a.angle(b)
  549.     return n,w
  550.     
  551.  
  552. # _bbCorners
  553. def _bbCorners(bb):
  554.     """Return the eight corners of a bounding box.
  555.  
  556.     bb is a BoundingBox object. The index of the returned points is
  557.     such that bit 0 determines if the point is minmal or maximal in x
  558.     direction. Bit 1 is the y direction and bit 2 the z
  559.     direction. This means point n is opposite to point (~n)&0x7.
  560.  
  561.     This is a helper function for the fitCone() function.
  562.     """
  563.     a,h = bb.getBounds()
  564.     b = vec3(h.x, a.y, a.z)
  565.     c = vec3(a.x, h.y, a.z)
  566.     d = vec3(h.x, h.y, a.z)
  567.     e = vec3(a.x, a.y, h.z)
  568.     f = vec3(h.x, a.y, h.z)
  569.     g = vec3(a.x, h.y, h.z)
  570.     return a,b,c,d,e,f,g,h
  571.  
  572.  
  573. # delete
  574. def delete(objs):
  575.     """Delete objects from the scene.
  576.  
  577.     Currently, this only works on WorldObjects...
  578.     """
  579.     objs = worldObjects(objs)
  580.     for obj in objs:
  581.         obj.parent.removeChild(obj)
  582.         
  583.  
  584. # convertToTriMesh
  585. def convertToTriMesh(objs):
  586.     """Convert one or more objects into triangle meshes.
  587.     """
  588.     objs = worldObjects(objs)
  589.     for obj in objs:
  590.         tm = trimeshgeom.TriMeshGeom()
  591.         obj.geom.convert(tm)
  592.         obj.geom = tm
  593.  
  594. # convertToPolyhedron
  595. def convertToPolyhedron(objs):
  596.     """Convert one or more objects into polyhedron objects.
  597.     """
  598.     objs = worldObjects(objs)
  599.     for obj in objs:
  600.         pg = polyhedrongeom.PolyhedronGeom()
  601.         obj.geom.convert(pg)
  602.         obj.geom = pg
  603.  
  604. # setupObjectNames
  605. def setupObjectNames():
  606.     """Create a string that can be executed to 'import' all scene names.
  607.     """
  608.     res = ""
  609.     valid_chars = string.ascii_letters + "_" + string.digits
  610.     for obj in getScene().item("WorldRoot").iterChilds():
  611.         varname = ""
  612.         for c in obj.name:
  613.             if c not in valid_chars:
  614.                 c = "_"
  615.             varname += c
  616.         if varname=="":
  617.             continue
  618.         if varname[0] in string.digits:
  619.             varname = "_"+varname[1:]
  620.         res += '%s = worldObject("%s")\n'%(varname, obj.name)
  621.     return res
  622.  
  623. # importDefaultPlugins
  624. def importDefaultPlugins():
  625.     """Import the default plugins.
  626.  
  627.     The plugin files/directories specified by the CGKIT_PLUGIN_PATH
  628.     environment variable (if it exists) are imported.
  629.     The function already outputs error messages and returns a list
  630.     of plugin descriptors.
  631.     """
  632.     s = os.environ.get("CGKIT_PLUGIN_PATH", "")
  633.     plugins = splitPaths(s)
  634.         
  635.     descs = pluginmanager.importPlugins(plugins)
  636.     
  637.     for desc in descs:
  638.         if desc.status!=pluginmanager.STATUS_OK:
  639.             sys.stderr.write(70*"-"+"\n")
  640.             sys.stderr.write('ERROR: Loading plugin "%s" failed:\n'%os.path.basename(desc.filename))
  641.             sys.stderr.write("\n"+desc.traceback)
  642.             sys.stderr.write(70*"-"+"\n")
  643.  
  644.     return descs
  645.             
  646. # splitPaths
  647. def splitPaths(paths):
  648.     """Split a string containing paths into the individual paths.
  649.     
  650.     The paths can either be separated by ':' or ';'. Windows drive
  651.     letters are maintained, even when ':' is used as separator.
  652.  
  653.     Example:
  654.  
  655.     \code
  656.     >>> splitPaths("&:c:\\shaders:c:\\more_shaders;")
  657.     ['&', 'c:\\shaders', 'c:\\more_shaders']    
  658.     \endcode
  659.     
  660.     \param paths (\c str) Paths string
  661.     \return A list with individual path strings.
  662.     """
  663.         
  664.     a = paths.split(";")
  665.     chunks = []
  666.     for s in a:
  667.         if s!="":
  668.             chunks += s.split(":")
  669.  
  670.     pathlist = []
  671.     while len(chunks)>0:
  672.         # Is this item only a path drive?
  673.         if len(chunks[0])==1 and chunks[0] in string.letters:
  674.             pathlist.append(":".join(chunks[:2]))
  675.             chunks = chunks[2:]
  676.         else:
  677.             pathlist.append(chunks[0])
  678.             chunks = chunks[1:]
  679.  
  680.     return map(lambda x: x.strip(), pathlist)
  681.  
  682. # group
  683. def group(*children, **keyargs):
  684.     """Group several world objects together.
  685.  
  686.     All non keyword arguments somehow refer to world objects that will
  687.     all be grouped together. An argument may either be a WorldObject,
  688.     the name of a world object or as a sequence of world objects or names.
  689.     The name of the new group may be given via the \a name keyword
  690.     argument.
  691.     The return value is the new Group object.
  692.     
  693.     \param name (\c str) Name of the group
  694.     \param children The world objects or their names.
  695.     """
  696.  
  697.     # Check the key arguments
  698.     for k in keyargs:
  699.         if k not in ["name"]:
  700.             raise TypeError, "Unknown keyword argument: '%s'"%k
  701.  
  702.     # "Flatten" the children list...
  703.     childs = []
  704.     for c in children:
  705.         if isinstance(c, types.StringTypes):
  706.             childs += [c]
  707.         else:
  708.             # Is c a sequence?
  709.             try:
  710.                 childs += list(c)
  711.             except TypeError:
  712.                 # obviously not...
  713.                 childs += [c]
  714.                 
  715.     childs = map(worldObject, childs)
  716.     name = keyargs.get("name", "group")
  717.     grp = group_module.Group(name=name, childs=childs)
  718.     return grp
  719.         
  720.  
  721. # ungroup
  722. def ungroup(group):
  723.     """Break up a group in its individual components.
  724.  
  725.     \param group (\c WorldObject or str) Group object
  726.     """
  727.     group = worldObject(group)
  728.     if group.geom!=None:
  729.         raise ValueError, 'Object "%s" is not a mere group. Remove the geometry before ungrouping.'%group.name
  730.  
  731.     # Move the children up one level
  732.     newparent = group.parent
  733.     for child in list(group.iterChilds()):
  734.         link(child, newparent)
  735.  
  736.     # Remove the group
  737.     newparent.removeChild(group)
  738.         
  739.  
  740. # replaceMaterial
  741. def replaceMaterial(name, newmat):
  742.     """Iterate over all world objects and replace a material with a new one.
  743.  
  744.     \param name (\c str) The name of an already assigned material
  745.     \param newmat (\c Material) The new material
  746.     """
  747.     for obj in getScene().walkWorld():
  748.         # Check all materials...
  749.         for i in range(obj.getNumMaterials()):
  750.             mat = obj.getMaterial(i)
  751.             if mat==None:
  752.                 continue
  753.             if mat.name==name:
  754.                 obj.setMaterial(newmat, i)
  755.     
  756.  
  757. # link
  758. def link(childs, parent=None, relative=False):
  759.     """Link all childs to parent.
  760.  
  761.     The function modifies the name of a child object if there would
  762.     be a clash with an existing object under the new parent.
  763.  
  764.     \param childs (\c list or WorldObject) A single WorldObject or a sequence of WorldObjects
  765.     \param parent (\c WorldObject) Parent object or None (=unlink)
  766.     \param relative (\c bool) If True the local child transform is not modified (i.e. it is interpreted as a relative position)
  767.     """
  768.  
  769.     # Check if childs is a sequence (other than a string)...
  770.     try:
  771.         len(childs)
  772.         if isinstance(childs, types.StringTypes):
  773.             is_sequence = False
  774.         else:
  775.             is_sequence = True
  776.     except:
  777.         is_sequence = False
  778.  
  779.     if not is_sequence:
  780.         childs = [childs]
  781.  
  782.     # No parent given? Then use the world root (=unlink)
  783.     if parent==None:
  784.         parent = getScene().worldRoot()
  785.     else:
  786.         parent = worldObject(parent)
  787.  
  788.     # Relink all childs...
  789.     for child in childs:
  790.         child = worldObject(child)
  791.         oldparent = child.parent
  792.         if oldparent!=None:
  793.             oldparent.removeChild(child)
  794.         if not relative:
  795.             Lp1 = oldparent.worldtransform
  796.             Lp2 = parent.worldtransform
  797.             child.transform = Lp2.inverse()*Lp1*child.transform
  798.         child.name = parent.makeChildNameUnique(child.name) 
  799.         parent.addChild(child)
  800.     
  801.  
  802. # drawClear
  803. def drawClear():
  804.     """Clear all drawing objects."""
  805.     # Clear all markers and lines
  806.     try:
  807.         drw = getScene().worldObject("Global_Draw")
  808.         drw.clear()
  809.     except KeyError:
  810.         pass
  811.  
  812.     # Clear all texts
  813.     try:
  814.         drw = getScene().worldObject("Global_DrawText")
  815.         drw.geom.clear()
  816.     except KeyError:
  817.         pass
  818.     
  819.     
  820. # drawMarker
  821. def drawMarker(pos, color=(1,1,1), size=1):
  822.     """Draw a marker.
  823.  
  824.     \param pos Marker position 
  825.     \param color Marker color
  826.     \param size Marker size (radius)
  827.     """
  828.     try:
  829.         drw = getScene().worldObject("Global_Draw")
  830.     except KeyError:
  831.         drw = draw.Draw(name="Global_Draw")
  832.     drw.marker(pos, color, size)
  833.  
  834. # drawLine
  835. def drawLine(pos1, pos2, color=(1,1,1), size=1):
  836.     """Draw a line.
  837.  
  838.     \param pos1 First line point
  839.     \param pos2 Second line point
  840.     \param color Line color
  841.     \param size Line width
  842.     """
  843.     try:
  844.         drw = getScene().worldObject("Global_Draw")
  845.     except KeyError:
  846.         drw = draw.Draw(name="Global_Draw")
  847.     drw.line(pos1, pos2, color, size)
  848.  
  849. # drawText
  850. def drawText(pos, txt, font=None, color=(1,1,1)):
  851.     """Draw a text string.
  852.  
  853.     font can be one of the constants defined in GLUT:
  854.  
  855.     - GLUT_BITMAP_8_BY_13
  856.     - GLUT_BITMAP_9_BY_15 (default)
  857.     - GLUT_BITMAP_TIMES_ROMAN_10
  858.     - GLUT_BITMAP_TIMES_ROMAN_24
  859.     - GLUT_BITMAP_HELVETICA_10
  860.     - GLUT_BITMAP_HELVETICA_12
  861.     - GLUT_BITMAP_HELVETICA_18
  862.  
  863.     \param pos Text position (3D)
  864.     \param txt The actual text
  865.     \param font A GLUT font constant
  866.     \param color Text color    
  867.     """
  868.     try:
  869.         drw = getScene().worldObject("Global_DrawText")
  870.     except KeyError:
  871.         drw = worldobject.WorldObject(name="Global_DrawText")
  872.         drw.geom = drawtextgeom.DrawTextGeom()
  873.     drw.geom.addText(pos, txt, font, color)
  874.  
  875. # listWorld
  876. def listWorld():
  877.     """List the contents of the world as a tree.
  878.     """
  879.     scn = getScene()
  880.     print "Root"
  881.     _listWorld(scn.worldRoot(), "")
  882.  
  883. def _listWorld(obj, lines):
  884.     """Helper function for the listWorld() command."""
  885.     childs = list(obj.iterChilds())
  886.     idx = 0
  887.     for child in childs:
  888.         if child.geom!=None:
  889.             g = child.geom.__class__.__name__
  890.         else:
  891.             g = "-"
  892.         print "%s+---%s (%s/%s)"%(lines,child.name,child.__class__.__name__, g)
  893.         if idx<len(childs)-1:
  894.             childlines = lines+"|   "
  895.         else:
  896.             childlines = lines+"    "
  897.         _listWorld(child, childlines)
  898.         idx += 1
  899.     
  900.  
  901. # load
  902. def load(filename, **options):
  903.     """Load a file.
  904.  
  905.     This function loads the given file without deleting the scene,
  906.     so the contents of the file is appended to the current scene.
  907.  
  908.     To be able to load the file there must be an appropriate import class
  909.     (protocol: "Import") available in the plugin manager. The class is
  910.     determined by examining the file extension. If no importer is
  911.     found a NoImporter exception is thrown.
  912.  
  913.     Any exception generated in the importer is passed to the caller.
  914.  
  915.     \param filename (\c str) File name
  916.     \param options Options that are passed to the import plugin
  917.     """
  918.     # Extract the extension (without '.') and make it lower case
  919.     ext = os.path.splitext(filename)[1][1:].lower()
  920.  
  921.     # Find the appropriate import plugin class by comparing the extension
  922.     for objdesc in pluginmanager.iterProtoObjects("Import"):
  923.         if ext in objdesc.object.extension():
  924.             break
  925.     else:
  926.         raise NoImporter('No import plugin found for extension "%s".'%ext)
  927.  
  928.     if not os.path.exists(filename):
  929.         raise IOError('File "%s" does not exist.'%filename)
  930.  
  931.     # Change into the directory of the given file
  932.     oldpath = os.getcwd()
  933.     dir = os.path.dirname(os.path.abspath(filename))
  934.     os.chdir(dir)
  935.  
  936.     # Import the file
  937.     imp = objdesc.object()
  938.     try:
  939.         imp.importFile(os.path.basename(filename), **options)
  940.     except:
  941.         os.chdir(oldpath)
  942.         raise
  943.         
  944.     # Change back to the previous directory
  945.     os.chdir(oldpath)
  946.  
  947.  
  948. # save
  949. def save(filename, **options):
  950.     """Save the scene to a file.
  951.  
  952.     This function saves the current scene.
  953.  
  954.     To be able to save the scene there must be an appropriate export class
  955.     (protocol: "Export") available in the plugin manager. The class is
  956.     determined by examining the file extension. If no exporter is
  957.     found a NoExporter exception is thrown.
  958.  
  959.     Any exception generated in the exporter is passed to the caller.
  960.  
  961.     \param filename (\c str) File name
  962.     \param options Options that are passed to the export plugin
  963.     """
  964.     # Extract the extension (without '.') and make it lower case
  965.     ext = os.path.splitext(filename)[1][1:].lower()
  966.  
  967.     # Find the appropriate export plugin class by comparing the extension
  968.     for objdesc in pluginmanager.iterProtoObjects("Export"):
  969.         if ext in objdesc.object.extension():
  970.             break
  971.     else:
  972.         raise NoExporter('No export plugin found for extension "%s".'%ext)
  973.  
  974.     # Change into the directory of the given file
  975.     oldpath = os.getcwd()
  976.     dir = os.path.dirname(os.path.abspath(filename))
  977.     os.chdir(dir)
  978.  
  979.     # Export the file
  980.     exp = objdesc.object()
  981.     exp.exportFile(os.path.basename(filename), **options)
  982.  
  983.     # Change back to the previous directory
  984.     os.chdir(oldpath)
  985.  
  986. # reset
  987. def reset():
  988.     """Reset an animation/simulation.
  989.  
  990.     Reset effectively sets the time back to 0.
  991.     """
  992.     eventmanager.eventManager().event(events.RESET)
  993.     
  994. # worldObjects
  995. def worldObjects(objs):
  996.     """Return a list of world objects.
  997.  
  998.     \a objs can be a string, a world object or a sequence of strings or
  999.     world objects. A string always referes to the world object with that
  1000.     name. The return value is always a list, even when only an individual
  1001.     object was specified as input.
  1002.  
  1003.     \param objs A sequence of strings/world objects or an individual string
  1004.            or world object.
  1005.     \return A list of WorldObject objects.
  1006.     """
  1007.  
  1008.     if isinstance(objs, types.StringTypes):
  1009.         objs = [objs]
  1010.  
  1011.     try:
  1012.         it = iter(objs)
  1013.     except TypeError:
  1014.         it = iter([objs])
  1015.         
  1016.     res = []
  1017.     for obj in it:
  1018.         res.append(worldObject(obj))
  1019.         
  1020.     return res
  1021.  
  1022. # worldObject
  1023. def worldObject(obj):
  1024.     """Return a world object.
  1025.  
  1026.     If \a obj is a string, the function returns the world object with
  1027.     that name. Otherwise \a obj is returned.
  1028.  
  1029.     \param obj An object or the name of an object.
  1030.     """
  1031.     
  1032.     if isinstance(obj, types.StringTypes):
  1033.         return getScene().worldObject(obj)
  1034.     else:
  1035.         return obj
  1036.  
  1037. # register
  1038. def register(*cmds):
  1039.     """Register functions in the cmds module.
  1040.  
  1041.     The given functions will be added to the cmds module.
  1042.  
  1043.     \code
  1044.     import cmds
  1045.     
  1046.     def func1():
  1047.         ...
  1048.  
  1049.     def func2(v):
  1050.         ...
  1051.  
  1052.     cmds.register(func1, func2)
  1053.             
  1054.     \endcode
  1055.     
  1056.     """
  1057.     # Add the commands to the 
  1058.     namespace = globals()
  1059.     for c in cmds:
  1060.         namespace[c.__name__] = c
  1061.