home *** CD-ROM | disk | FTP | other *** search
/ Chip 2011 November / CHIP_2011_11.iso / Programy / Narzedzia / Inkscape / Inkscape-0.48.2-1-win32.exe / share / extensions / polyhedron_3d.py < prev    next >
Text File  |  2011-07-08  |  23KB  |  524 lines

  1. #!/usr/bin/env python 
  2. '''
  3. Copyright (C) 2007 John Beard john.j.beard@gmail.com
  4.  
  5. ##This extension draws 3d objects from a Wavefront .obj 3D file stored in a local folder
  6. ##Many settings for appearance, lighting, rotation, etc are available.
  7.  
  8. #                              ^y
  9. #                              |
  10. #        __--``|               |_--``|     __--
  11. #  __--``      |         __--``|     |_--``
  12. # |       z    |        |      |_--``|
  13. # |       <----|--------|-----_0-----|----------------
  14. # |            |        |_--`` |     |
  15. # |      __--``     <-``|      |_--``
  16. # |__--``           x   |__--``|
  17. #   IMAGE PLANE           SCENE|
  18. #                              |
  19.  
  20. #Vertices are given as "v" followed by three numbers (x,y,z).
  21. #All files need a vertex list
  22. #v  x.xxx   y.yyy   z.zzz
  23.  
  24. #Faces are given by a list of vertices
  25. #(vertex 1 is the first in the list above, 2 the second, etc):
  26. #f  1   2   3
  27.  
  28. #Edges are given by a list of vertices. These will be broken down
  29. #into adjacent pairs automatically.
  30. #l  1   2   3
  31.  
  32. #Faces are rendered according to the painter's algorithm and perhaps
  33. #back-face culling, if selected. The parameter to sort the faces by
  34. #is user-selectable between max, min and average z-value of the vertices
  35.  
  36. ######LICENCE#######
  37. This program is free software; you can redistribute it and/or modify
  38. it under the terms of the GNU General Public License as published by
  39. the Free Software Foundation; either version 2 of the License, or
  40. (at your option) any later version.
  41.  
  42. This program is distributed in the hope that it will be useful,
  43. but WITHOUT ANY WARRANTY; without even the implied warranty of
  44. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  45. GNU General Public License for more details.
  46.  
  47. You should have received a copy of the GNU General Public License
  48. along with this program; if not, write to the Free Software
  49. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  50. '''
  51.  
  52. import inkex
  53. import simplestyle, sys, re
  54. from math import *
  55. import gettext
  56. _ = gettext.gettext
  57. try:
  58.     from numpy import *
  59. except:
  60.     inkex.errormsg(_("Failed to import the numpy module. This module is required by this extension. Please install it and try again.  On a Debian-like system this can be done with the command 'sudo apt-get install python-numpy'."))
  61.     sys.exit()
  62.  
  63. #FILE IO ROUTINES
  64. def get_filename(self_options):
  65.         if self_options.obj == 'from_file':
  66.             file = self_options.spec_file
  67.         else:
  68.             file = self_options.obj + '.obj'
  69.             
  70.         return file
  71.  
  72. def objfile(name):
  73.     import os.path
  74.     if __name__ == '__main__':
  75.         filename = sys.argv[0]
  76.     else:
  77.         filename = __file__
  78.     path = os.path.abspath(os.path.dirname(filename))
  79.     path = os.path.join(path, 'Poly3DObjects', name)
  80.     return path
  81.     
  82. def get_obj_data(obj, name):
  83.     infile = open(objfile(name))
  84.     
  85.     #regular expressions
  86.     getname = '(.[nN]ame:\\s*)(.*)'
  87.     floating = '([\-\+\\d*\.e]*)'   #a possibly non-integer number, with +/- and exponent.
  88.     getvertex = '(v\\s+)'+floating+'\\s+'+floating+'\\s+'+floating
  89.     getedgeline = '(l\\s+)(.*)'
  90.     getfaceline = '(f\\s+)(.*)'
  91.     getnextint = '(\\d+)([/\\d]*)(.*)'#we need to deal with 123\343\123 or 123\\456 as equivalent to 123 (we are ignoring the other options in the obj file)
  92.     
  93.     for line in infile:
  94.         if line[0]=='#':                    #we have a comment line
  95.             m = re.search(getname, line)        #check to see if this line contains a name
  96.             if m:
  97.                 obj.name = m.group(2)           #if it does, set the property
  98.         elif line[0] == 'v':                #we have a vertex (maybe)
  99.             m = re.search(getvertex, line)      #check to see if this line contains a valid vertex
  100.             if m:                               #we have a valid vertex
  101.                 obj.vtx.append( [float(m.group(2)), float(m.group(3)), float(m.group(4)) ] )
  102.         elif line[0] == 'l':                #we have a line (maybe)
  103.             m = re.search(getedgeline, line)    #check to see if this line begins 'l '
  104.             if m:                               #we have a line beginning 'l '
  105.                 vtxlist = []    #buffer
  106.                 while line:
  107.                     m2 = re.search(getnextint, line)
  108.                     if m2:
  109.                         vtxlist.append( int(m2.group(1)) )
  110.                         line = m2.group(3)#remainder
  111.                     else:
  112.                         line = None
  113.                 if len(vtxlist) > 1:#we need at least 2 vertices to make an edge
  114.                     for i in range (len(vtxlist)-1):#we can have more than one vertex per line - get adjacent pairs
  115.                         obj.edg.append( ( vtxlist[i], vtxlist[i+1] ) )#get the vertex pair between that vertex and the next
  116.         elif line[0] == 'f':                #we have a face (maybe)
  117.             m = re.search(getfaceline, line)
  118.             if m:                               #we have a line beginning 'f '
  119.                 vtxlist = []#buffer
  120.                 while line:
  121.                     m2 = re.search(getnextint, line)
  122.                     if m2:
  123.                         vtxlist.append( int(m2.group(1)) )
  124.                         line = m2.group(3)#remainder
  125.                     else:
  126.                         line = None
  127.                 if len(vtxlist) > 2:            #we need at least 3 vertices to make an edge
  128.                     obj.fce.append(vtxlist)
  129.     
  130.     if obj.name == '':#no name was found, use filename, without extension (.obj)
  131.         obj.name = name[0:-4]
  132.  
  133. #RENDERING AND SVG OUTPUT FUNCTIONS
  134.  
  135. def draw_SVG_dot((cx, cy), st, name, parent):
  136.     style = { 'stroke': '#000000', 'stroke-width':str(st.th), 'fill': st.fill, 'stroke-opacity':st.s_opac, 'fill-opacity':st.f_opac}
  137.     circ_attribs = {'style':simplestyle.formatStyle(style),
  138.                     inkex.addNS('label','inkscape'):name,
  139.                     'r':str(st.r),
  140.                     'cx':str(cx), 'cy':str(-cy)}
  141.     inkex.etree.SubElement(parent, inkex.addNS('circle','svg'), circ_attribs )
  142.     
  143. def draw_SVG_line((x1, y1),(x2, y2), st, name, parent):
  144.     style = { 'stroke': '#000000', 'stroke-width':str(st.th), 'stroke-linecap':st.linecap}
  145.     line_attribs = {'style':simplestyle.formatStyle(style),
  146.                     inkex.addNS('label','inkscape'):name,
  147.                     'd':'M '+str(x1)+','+str(-y1)+' L '+str(x2)+','+str(-y2)}
  148.     inkex.etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
  149.     
  150. def draw_SVG_poly(pts, face, st, name, parent):
  151.     style = { 'stroke': '#000000', 'stroke-width':str(st.th), 'stroke-linejoin':st.linejoin, \
  152.               'stroke-opacity':st.s_opac, 'fill': st.fill, 'fill-opacity':st.f_opac}   
  153.     for i in range(len(face)):
  154.         if i == 0:#for first point
  155.             d = 'M'#move to
  156.         else:
  157.             d = d + 'L'#line to
  158.         d = d+ str(pts[face[i]-1][0]) + ',' + str(-pts[face[i]-1][1])#add point
  159.     d = d + 'z' #close the polygon
  160.     
  161.     line_attribs = {'style':simplestyle.formatStyle(style),
  162.                     inkex.addNS('label','inkscape'):name,'d': d}
  163.     inkex.etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
  164.     
  165. def draw_edges( edge_list, pts, st, parent ):
  166.     for edge in edge_list:#for every edge
  167.         pt_1 = pts[ edge[0]-1 ][0:2] #the point at the start
  168.         pt_2 = pts[ edge[1]-1 ][0:2] #the point at the end
  169.         name = 'Edge'+str(edge[0])+'-'+str(edge[1])
  170.         draw_SVG_line(pt_1,pt_2,st, name, parent)#plot edges
  171.                               
  172. def draw_faces( faces_data, pts, obj, shading, fill_col,st, parent):          
  173.     for face in faces_data:#for every polygon that has been sorted
  174.         if shading:
  175.             st.fill = get_darkened_colour(fill_col, face[1]/pi)#darken proportionally to angle to lighting vector
  176.         else:
  177.             st.fill = get_darkened_colour(fill_col, 1)#do not darken colour
  178.                           
  179.         face_no = face[3]#the number of the face to draw
  180.         draw_SVG_poly(pts, obj.fce[ face_no ], st, 'Face:'+str(face_no), parent)
  181.  
  182. def get_darkened_colour( (r,g,b), factor):
  183. #return a hex triplet of colour, reduced in lightness proportionally to a value between 0 and 1
  184.     return  '#' + "%02X" % floor( factor*r ) \
  185.                 + "%02X" % floor( factor*g ) \
  186.                 + "%02X" % floor( factor*b ) #make the colour string
  187.  
  188. def make_rotation_log(options):
  189. #makes a string recording the axes and angles of each roation, so an object can be repeated
  190.     return   options.r1_ax+str('%.2f'%options.r1_ang)+':'+\
  191.              options.r2_ax+str('%.2f'%options.r2_ang)+':'+\
  192.              options.r3_ax+str('%.2f'%options.r3_ang)+':'+\
  193.              options.r1_ax+str('%.2f'%options.r4_ang)+':'+\
  194.              options.r2_ax+str('%.2f'%options.r5_ang)+':'+\
  195.              options.r3_ax+str('%.2f'%options.r6_ang)
  196.  
  197. #MATHEMATICAL FUNCTIONS
  198. def get_angle( vector1, vector2 ): #returns the angle between two vectors
  199.     return acos( dot(vector1, vector2) )
  200.  
  201. def length(vector):#return the pythagorean length of a vector
  202.     return sqrt(dot(vector,vector))
  203.  
  204. def normalise(vector):#return the unit vector pointing in the same direction as the argument
  205.     return vector / length(vector)
  206.  
  207. def get_normal( pts, face): #returns the normal vector for the plane passing though the first three elements of face of pts
  208.     #n = pt[0]->pt[1] x pt[0]->pt[3]
  209.     a = (array(pts[ face[0]-1 ]) - array(pts[ face[1]-1 ]))
  210.     b = (array(pts[ face[0]-1 ]) - array(pts[ face[2]-1 ]))
  211.     return cross(a,b).flatten()
  212.  
  213. def get_unit_normal(pts, face, cw_wound): #returns the unit normal for the plane passing through the first three points of face, taking account of winding
  214.     if cw_wound:
  215.         winding = -1 #if it is clockwise wound, reverse the vecotr direction
  216.     else:
  217.         winding = 1 #else leave alone
  218.     
  219.     return winding*normalise(get_normal(pts, face))
  220.  
  221. def rotate( matrix, angle, axis ):#choose the correct rotation matrix to use
  222.     if   axis == 'x':
  223.         matrix = rot_x(matrix, angle)
  224.     elif axis == 'y':
  225.         matrix = rot_y(matrix, angle)
  226.     elif axis == 'z':
  227.         matrix = rot_z(matrix, angle)
  228.     return matrix
  229.     
  230. def rot_z( matrix , a):#rotate around the z-axis by a radians
  231.     trans_mat = mat(array( [[ cos(a) , -sin(a) ,    0   ],
  232.                             [ sin(a) ,  cos(a) ,    0   ],
  233.                             [   0    ,    0    ,    1   ]]))
  234.     return trans_mat*matrix
  235.  
  236. def rot_y( matrix , a):#rotate around the y-axis by a radians
  237.     trans_mat = mat(array( [[ cos(a) ,    0    , sin(a) ],
  238.                             [   0    ,    1    ,    0   ],
  239.                             [-sin(a) ,    0    , cos(a) ]]))
  240.     return trans_mat*matrix
  241.     
  242. def rot_x( matrix , a):#rotate around the x-axis by a radians
  243.     trans_mat = mat(array( [[   1    ,    0    ,    0   ],
  244.                             [   0    ,  cos(a) ,-sin(a) ],
  245.                             [   0    ,  sin(a) , cos(a) ]]))
  246.     return trans_mat*matrix
  247.  
  248. def get_transformed_pts( vtx_list, trans_mat):#translate the points according to the matrix
  249.     transformed_pts = []
  250.     for vtx in vtx_list:
  251.         transformed_pts.append((trans_mat * mat(vtx).T).T.tolist()[0] )#transform the points at add to the list
  252.     return transformed_pts
  253.  
  254. def get_max_z(pts, face): #returns the largest z_value of any point in the face
  255.     max_z = pts[ face[0]-1 ][2]
  256.     for i in range(1, len(face)):
  257.         if pts[ face[0]-1 ][2] >= max_z:
  258.             max_z = pts[ face[0]-1 ][2]
  259.     return max_z
  260.     
  261. def get_min_z(pts, face): #returns the smallest z_value of any point in the face
  262.     min_z = pts[ face[0]-1 ][2]
  263.     for i in range(1, len(face)):
  264.         if pts[ face[i]-1 ][2] <= min_z:
  265.             min_z = pts[ face[i]-1 ][2]
  266.     return min_z
  267.     
  268. def get_cent_z(pts, face): #returns the centroid z_value of any point in the face
  269.     sum = 0
  270.     for i in range(len(face)):
  271.             sum += pts[ face[i]-1 ][2]
  272.     return sum/len(face)
  273.     
  274. def get_z_sort_param(pts, face, method): #returns the z-sorting parameter specified by 'method' ('max', 'min', 'cent')    
  275.     z_sort_param = ''
  276.     if  method == 'max':
  277.         z_sort_param  = get_max_z(pts, face)
  278.     elif method == 'min':
  279.         z_sort_param  = get_min_z(pts, face)
  280.     else:
  281.         z_sort_param  = get_cent_z(pts, face)
  282.     return z_sort_param
  283.  
  284. #OBJ DATA MANIPULATION
  285. def remove_duplicates(list):#removes the duplicates from a list
  286.     list.sort()#sort the list
  287.  
  288.     last = list[-1]
  289.     for i in range(len(list)-2, -1, -1):
  290.         if last==list[i]:
  291.             del list[i]
  292.         else:
  293.             last = list[i]
  294.     return list
  295.  
  296. def make_edge_list(face_list):#make an edge vertex list from an existing face vertex list
  297.     edge_list = []
  298.     for i in range(len(face_list)):#for every face
  299.         edges = len(face_list[i]) #number of edges around that face
  300.         for j in range(edges):#for every vertex in that face
  301.             new_edge = [face_list[i][j], face_list[i][(j+1)%edges] ]
  302.             new_edge.sort() #put in ascending order of vertices (to ensure we spot duplicates)
  303.             edge_list.append( new_edge )#get the vertex pair between that vertex and the next
  304.     
  305.     return remove_duplicates(edge_list)
  306.     
  307. class Style(object): #container for style information
  308.     def __init__(self,options):
  309.         self.th = options.th
  310.         self.fill= '#ff0000'
  311.         self.col = '#000000'
  312.         self.r = 2
  313.         self.f_opac = str(options.f_opac/100.0)
  314.         self.s_opac = str(options.s_opac/100.0)
  315.         self.linecap = 'round'
  316.         self.linejoin = 'round'
  317.  
  318. class Obj(object): #a 3d object defined by the vertices and the faces (eg a polyhedron)
  319. #edges can be generated from this information
  320.     def __init__(self):
  321.         self.vtx = []
  322.         self.edg = []
  323.         self.fce = []
  324.         self.name=''
  325.         
  326.     def set_type(self, options):
  327.         if options.type == 'face':
  328.             if self.fce != []:
  329.                 self.type = 'face'
  330.             else:
  331.                 inkex.errormsg(_('No face data found in specified file.'))
  332.                 inkex.errormsg(_('Try selecting "Edge Specified" in the Model File tab.\n'))
  333.                 self.type = 'error'
  334.         else:
  335.             if self.edg != []:
  336.                 self.type = 'edge'
  337.             else:
  338.                 inkex.errormsg(_('No edge data found in specified file.'))
  339.                 inkex.errormsg(_('Try selecting "Face Specified" in the Model File tab.\n'))
  340.                 self.type = 'error'
  341.  
  342. class Poly_3D(inkex.Effect):
  343.     def __init__(self):
  344.         inkex.Effect.__init__(self)
  345.         self.OptionParser.add_option("--tab",
  346.             action="store", type="string", 
  347.             dest="tab", default="object") 
  348.  
  349. #MODEL FILE SETTINGS
  350.         self.OptionParser.add_option("--obj",
  351.             action="store", type="string", 
  352.             dest="obj", default='cube')
  353.         self.OptionParser.add_option("--spec_file",
  354.             action="store", type="string", 
  355.             dest="spec_file", default='great_rhombicuboct.obj')
  356.         self.OptionParser.add_option("--cw_wound",
  357.             action="store", type="inkbool", 
  358.             dest="cw_wound", default='true')
  359.         self.OptionParser.add_option("--type",
  360.             action="store", type="string", 
  361.             dest="type", default='face')
  362. #VEIW SETTINGS
  363.         self.OptionParser.add_option("--r1_ax",
  364.             action="store", type="string", 
  365.             dest="r1_ax", default=0)
  366.         self.OptionParser.add_option("--r2_ax",
  367.             action="store", type="string", 
  368.             dest="r2_ax", default=0)
  369.         self.OptionParser.add_option("--r3_ax",
  370.             action="store", type="string", 
  371.             dest="r3_ax", default=0)
  372.         self.OptionParser.add_option("--r4_ax",
  373.             action="store", type="string", 
  374.             dest="r4_ax", default=0)
  375.         self.OptionParser.add_option("--r5_ax",
  376.             action="store", type="string", 
  377.             dest="r5_ax", default=0)
  378.         self.OptionParser.add_option("--r6_ax",
  379.             action="store", type="string", 
  380.             dest="r6_ax", default=0)
  381.         self.OptionParser.add_option("--r1_ang",
  382.             action="store", type="float", 
  383.             dest="r1_ang", default=0)
  384.         self.OptionParser.add_option("--r2_ang",
  385.             action="store", type="float", 
  386.             dest="r2_ang", default=0)
  387.         self.OptionParser.add_option("--r3_ang",
  388.             action="store", type="float", 
  389.             dest="r3_ang", default=0)
  390.         self.OptionParser.add_option("--r4_ang",
  391.             action="store", type="float", 
  392.             dest="r4_ang", default=0)
  393.         self.OptionParser.add_option("--r5_ang",
  394.             action="store", type="float", 
  395.             dest="r5_ang", default=0)
  396.         self.OptionParser.add_option("--r6_ang",
  397.             action="store", type="float", 
  398.             dest="r6_ang", default=0)
  399.         self.OptionParser.add_option("--scl",
  400.             action="store", type="float", 
  401.             dest="scl", default=100.0)
  402. #STYLE SETTINGS
  403.         self.OptionParser.add_option("--show",
  404.             action="store", type="string", 
  405.             dest="show", default='faces')
  406.         self.OptionParser.add_option("--shade",
  407.             action="store", type="inkbool", 
  408.             dest="shade", default='true')
  409.         self.OptionParser.add_option("--f_r",
  410.             action="store", type="int", 
  411.             dest="f_r", default=255)
  412.         self.OptionParser.add_option("--f_g",
  413.             action="store", type="int", 
  414.             dest="f_g", default=0)
  415.         self.OptionParser.add_option("--f_b",
  416.             action="store", type="int", 
  417.             dest="f_b", default=0)
  418.         self.OptionParser.add_option("--f_opac",
  419.             action="store", type="int", 
  420.             dest="f_opac", default=100)
  421.         self.OptionParser.add_option("--s_opac",
  422.             action="store", type="int", 
  423.             dest="s_opac", default=100)
  424.         self.OptionParser.add_option("--th",
  425.             action="store", type="float", 
  426.             dest="th", default=2)
  427.         self.OptionParser.add_option("--lv_x",
  428.             action="store", type="float", 
  429.             dest="lv_x", default=1)
  430.         self.OptionParser.add_option("--lv_y",
  431.             action="store", type="float", 
  432.             dest="lv_y", default=1)
  433.         self.OptionParser.add_option("--lv_z",
  434.             action="store", type="float", 
  435.             dest="lv_z", default=-2)
  436.         self.OptionParser.add_option("--back",
  437.             action="store", type="inkbool", 
  438.             dest="back", default='false')
  439.         self.OptionParser.add_option("--norm",
  440.             action="store", type="inkbool", 
  441.             dest="norm", default='true')
  442.         self.OptionParser.add_option("--z_sort",
  443.             action="store", type="string", 
  444.             dest="z_sort", default='min')
  445.             
  446.             
  447.     def effect(self):
  448.         so = self.options#shorthand
  449.         
  450.         #INITIALISE AND LOAD DATA
  451.         
  452.         obj = Obj() #create the object
  453.         file = get_filename(so)#get the file to load data from
  454.         get_obj_data(obj, file)#load data from the obj file
  455.         obj.set_type(so)#set the type (face or edge) as per the settings
  456.         
  457.         st = Style(so) #initialise style
  458.         fill_col = (so.f_r, so.f_g, so.f_b) #colour tuple for the face fill
  459.         lighting = normalise( (so.lv_x,-so.lv_y,so.lv_z) ) #unit light vector
  460.         
  461.         #INKSCAPE GROUP TO CONTAIN THE POLYHEDRON
  462.         
  463.         #Put in in the centre of the current view
  464.         poly_transform = 'translate(' + str( self.view_center[0]) + ',' + str( self.view_center[1]) + ')'
  465.         #we will put all the rotations in the object name, so it can be repeated in 
  466.         poly_name = obj.name+':'+make_rotation_log(so)
  467.         poly_attribs = {inkex.addNS('label','inkscape'):poly_name,
  468.                         'transform':poly_transform }
  469.         poly = inkex.etree.SubElement(self.current_layer, 'g', poly_attribs)#the group to put everything in
  470.         
  471.         #TRANFORMATION OF THE OBJECT (ROTATION, SCALE, ETC)
  472.         
  473.         trans_mat = mat(identity(3, float)) #init. trans matrix as identity matrix
  474.         for i in range(1, 7):#for each rotation
  475.             axis  = eval('so.r'+str(i)+'_ax')
  476.             angle = eval('so.r'+str(i)+'_ang') *pi/180
  477.             trans_mat = rotate(trans_mat, angle, axis)
  478.         trans_mat = trans_mat*so.scl #scale by linear factor (do this only after the transforms to reduce round-off)
  479.         
  480.         transformed_pts = get_transformed_pts(obj.vtx, trans_mat) #the points as projected in the z-axis onto the viewplane
  481.         
  482.         #RENDERING OF THE OBJECT
  483.         
  484.         if so.show == 'vtx':
  485.             for i in range(len(transformed_pts)):
  486.                 draw_SVG_dot([transformed_pts[i][0],transformed_pts[i][1]], st, 'Point'+str(i), poly)#plot points using transformed_pts x and y coords
  487.         
  488.         elif so.show == 'edg':
  489.             if obj.type == 'face':#we must generate the edge list from the faces
  490.                 edge_list = make_edge_list(obj.fce)
  491.             else:#we already have an edge list
  492.                 edge_list = obj.edg
  493.                         
  494.             draw_edges( edge_list, transformed_pts, st, poly)
  495.                               
  496.         elif so.show == 'fce':
  497.             if obj.type == 'face':#we have a face list
  498.                
  499.                 z_list = []
  500.                 
  501.                 for i in range(len(obj.fce)):
  502.                     face = obj.fce[i] #the face we are dealing with
  503.                     norm = get_unit_normal(transformed_pts, face, so.cw_wound) #get the normal vector to the face
  504.                     angle = get_angle( norm, lighting )#get the angle between the normal and the lighting vector
  505.                     z_sort_param = get_z_sort_param(transformed_pts, face, so.z_sort)
  506.                     
  507.                     if so.back or norm[2] > 0: # include all polygons or just the front-facing ones as needed
  508.                         z_list.append((z_sort_param, angle, norm, i))#record the maximum z-value of the face and angle to light, along with the face ID and normal
  509.                 
  510.                 z_list.sort(lambda x, y: cmp(x[0],y[0])) #sort by ascending sort parameter of the face
  511.                 draw_faces( z_list, transformed_pts, obj, so.shade, fill_col, st, poly)
  512.  
  513.             else:#we cannot generate a list of faces from the edges without a lot of computation
  514.                 inkex.errormsg(_('Face Data Not Found. Ensure file contains face data, and check the file is imported as "Face-Specified" under the "Model File" tab.\n'))
  515.         else:
  516.             inkex.errormsg(_('Internal Error. No view type selected\n'))
  517.         
  518. if __name__ == '__main__':
  519.     e = Poly_3D()
  520.     e.affect()
  521.  
  522.  
  523. # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
  524.