Copyright (C) 2007 John Beard john.j.beard@gmail.com
##This extension draws 3d objects from a Wavefront .obj 3D file stored in a local folder
##Many settings for appearance, lighting, rotation, etc are available.
# ^y
# |
# __--``| |_--``| __--
# __--`` | __--``| |_--``
# | z | | |_--``|
# | <----|--------|-----_0-----|----------------
# | | |_--`` | |
# | __--`` <-``| |_--``
# |__--`` x |__--``|
# IMAGE PLANE SCENE|
# |
#Vertices are given as "v" followed by three numbers (x,y,z).
#All files need a vertex list
#v x.xxx y.yyy z.zzz
#Faces are given by a list of vertices
#(vertex 1 is the first in the list above, 2 the second, etc):
#f 1 2 3
#Edges are given by a list of vertices. These will be broken down
#into adjacent pairs automatically.
#l 1 2 3
#Faces are rendered according to the painter's algorithm and perhaps
#back-face culling, if selected. The parameter to sort the faces by
#is user-selectable between max, min and average z-value of the vertices
######LICENCE#######
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
import inkex
import simplestyle, sys, re
from math import *
import gettext
_ = gettext.gettext
try:
from numpy import *
except:
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'."))
sys.exit()
#FILE IO ROUTINES
def get_filename(self_options):
if self_options.obj == 'from_file':
file = self_options.spec_file
else:
file = self_options.obj + '.obj'
return file
def objfile(name):
import os.path
if __name__ == '__main__':
filename = sys.argv[0]
else:
filename = __file__
path = os.path.abspath(os.path.dirname(filename))
path = os.path.join(path, 'Poly3DObjects', name)
return path
def get_obj_data(obj, name):
infile = open(objfile(name))
#regular expressions
getname = '(.[nN]ame:\\s*)(.*)'
floating = '([\-\+\\d*\.e]*)' #a possibly non-integer number, with +/- and exponent.
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)
for line in infile:
if line[0]=='#': #we have a comment line
m = re.search(getname, line) #check to see if this line contains a name
if m:
obj.name = m.group(2) #if it does, set the property
elif line[0] == 'v': #we have a vertex (maybe)
m = re.search(getvertex, line) #check to see if this line contains a valid vertex
def get_angle( vector1, vector2 ): #returns the angle between two vectors
return acos( dot(vector1, vector2) )
def length(vector):#return the pythagorean length of a vector
return sqrt(dot(vector,vector))
def normalise(vector):#return the unit vector pointing in the same direction as the argument
return vector / length(vector)
def get_normal( pts, face): #returns the normal vector for the plane passing though the first three elements of face of pts
#n = pt[0]->pt[1] x pt[0]->pt[3]
a = (array(pts[ face[0]-1 ]) - array(pts[ face[1]-1 ]))
b = (array(pts[ face[0]-1 ]) - array(pts[ face[2]-1 ]))
return cross(a,b).flatten()
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
if cw_wound:
winding = -1 #if it is clockwise wound, reverse the vecotr direction
else:
winding = 1 #else leave alone
return winding*normalise(get_normal(pts, face))
def rotate( matrix, angle, axis ):#choose the correct rotation matrix to use
if axis == 'x':
matrix = rot_x(matrix, angle)
elif axis == 'y':
matrix = rot_y(matrix, angle)
elif axis == 'z':
matrix = rot_z(matrix, angle)
return matrix
def rot_z( matrix , a):#rotate around the z-axis by a radians
trans_mat = mat(array( [[ cos(a) , -sin(a) , 0 ],
[ sin(a) , cos(a) , 0 ],
[ 0 , 0 , 1 ]]))
return trans_mat*matrix
def rot_y( matrix , a):#rotate around the y-axis by a radians
trans_mat = mat(array( [[ cos(a) , 0 , sin(a) ],
[ 0 , 1 , 0 ],
[-sin(a) , 0 , cos(a) ]]))
return trans_mat*matrix
def rot_x( matrix , a):#rotate around the x-axis by a radians
trans_mat = mat(array( [[ 1 , 0 , 0 ],
[ 0 , cos(a) ,-sin(a) ],
[ 0 , sin(a) , cos(a) ]]))
return trans_mat*matrix
def get_transformed_pts( vtx_list, trans_mat):#translate the points according to the matrix
transformed_pts = []
for vtx in vtx_list:
transformed_pts.append((trans_mat * mat(vtx).T).T.tolist()[0] )#transform the points at add to the list
return transformed_pts
def get_max_z(pts, face): #returns the largest z_value of any point in the face
max_z = pts[ face[0]-1 ][2]
for i in range(1, len(face)):
if pts[ face[0]-1 ][2] >= max_z:
max_z = pts[ face[0]-1 ][2]
return max_z
def get_min_z(pts, face): #returns the smallest z_value of any point in the face
min_z = pts[ face[0]-1 ][2]
for i in range(1, len(face)):
if pts[ face[i]-1 ][2] <= min_z:
min_z = pts[ face[i]-1 ][2]
return min_z
def get_cent_z(pts, face): #returns the centroid z_value of any point in the face
sum = 0
for i in range(len(face)):
sum += pts[ face[i]-1 ][2]
return sum/len(face)
def get_z_sort_param(pts, face, method): #returns the z-sorting parameter specified by 'method' ('max', 'min', 'cent')
z_sort_param = ''
if method == 'max':
z_sort_param = get_max_z(pts, face)
elif method == 'min':
z_sort_param = get_min_z(pts, face)
else:
z_sort_param = get_cent_z(pts, face)
return z_sort_param
#OBJ DATA MANIPULATION
def remove_duplicates(list):#removes the duplicates from a list
list.sort()#sort the list
last = list[-1]
for i in range(len(list)-2, -1, -1):
if last==list[i]:
del list[i]
else:
last = list[i]
return list
def make_edge_list(face_list):#make an edge vertex list from an existing face vertex list
edge_list = []
for i in range(len(face_list)):#for every face
edges = len(face_list[i]) #number of edges around that face
for j in range(edges):#for every vertex in that face
else:#we cannot generate a list of faces from the edges without a lot of computation
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'))
else:
inkex.errormsg(_('Internal Error. No view type selected\n'))