# ***** 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: objimport.py,v 1.11 2006/03/20 19:33:24 mbaas Exp $
import os.path, sys
from cgtypes import *
from worldobject import WorldObject
from trimesh import TriMesh
from trimeshgeom import TriMeshGeom
from polyhedron import Polyhedron
from polyhedrongeom import PolyhedronGeom
import pluginmanager
from cgkit.all import UNIFORM,VARYING,FACEVARYING,NORMAL,FLOAT,INT,OBJMaterial,OBJTextureMap
import cmds
import objmtl
class _MTLReader(objmtl.MTLReader):
"""Read MTL files and create a list of materials.
Reads an MTL file and stores the materials as OBJMaterial objects
in the list self.material.
"""
def __init__(self):
objmtl.MTLReader.__init__(self)
self.materials = []
self.currentmat = None
def begin(self):
self.materials = []
self.currentmat = None
def end(self):
pass
def newmtl(self, name):
mat = OBJMaterial(name=name)
self.materials.append(mat)
self.currentmat = mat
def illum(self, model):
if self.currentmat!=None:
self.currentmat.illum = model
def Ka(self, c):
if self.currentmat!=None:
self.currentmat.Ka = c
def Kd(self, c):
if self.currentmat!=None:
self.currentmat.Kd = c
def Ks(self, c):
if self.currentmat!=None:
self.currentmat.Ks = c
def Ks(self, c):
if self.currentmat!=None:
self.currentmat.Ks = c
def Ke(self, c):
if self.currentmat!=None:
self.currentmat.Ke = c
def Ns(self, shininess):
if self.currentmat!=None:
self.currentmat.Ns = shininess
def Ni(self, refl):
if self.currentmat!=None:
self.currentmat.Ni = refl
def d(self, alpha):
if self.currentmat!=None:
self.currentmat.d = alpha
def Tr(self, alpha):
if self.currentmat!=None:
self.currentmat.Tr = alpha
def Tf(self, c):
if self.currentmat!=None:
self.currentmat.Tf = c
def sharpness(self, v):
if self.currentmat!=None:
self.currentmat.sharpness = v
def map_Ka(self, name, options):
if self.currentmat!=None:
self.currentmat.map_Ka = self.createMap(name, options)
def map_Kd(self, name, options):
if self.currentmat!=None:
self.currentmat.map_Kd = self.createMap(name, options)
def map_Ks(self, name, options):
if self.currentmat!=None:
self.currentmat.map_Ks = self.createMap(name, options)
def map_Ke(self, name, options):
if self.currentmat!=None:
self.currentmat.map_Ke = self.createMap(name, options)
def map_Ns(self, name, options):
if self.currentmat!=None:
self.currentmat.map_Ns = self.createMap(name, options)
def map_d(self, name, options):
if self.currentmat!=None:
self.currentmat.map_d = self.createMap(name, options)
def map_Bump(self, name, options):
if self.currentmat!=None:
map = self.createMap(name, options)
map.bumpsize = options.get("-bm", 1.0)
self.currentmat.map_Bump = map
def refl(self, name, options):
if self.currentmat!=None:
map = self.createMap(name, options)
map.refltype = options.get("-type", "sphere")
self.currentmat.refl.append(map)
def createMap(self, name, options):
map = OBJTextureMap(filename = name,
offset = options.get("-o", (0,0,0)),
scale = options.get("-s", (1,1,1)),
turb = options.get("-t", (0,0,0)),
mm = options.get("-mm", (0,1.0)),
clamp = options.get("-clamp", False),
blendu = options.get("-blendu", True),
blendv = options.get("-blendv", True))
return map
class _OBJReader(objmtl.OBJReader):
"""Read an OBJ file.
"""
def __init__(self, root=None):
objmtl.OBJReader.__init__(self)
# Dummy vertex at position 0 because the indices in an OBJ file
# start with 1
self.verts = [vec3(0)]
self.normals = [vec3(0)]
self.tverts = [vec3(0)]
# Local object hierarchy. Each "node" is a 2-tuple (WorldObject,
# Childs) where Childs is a dictionary that contains the children
# "nodes". The value is the object name.
self.hierarchy = (root, {})
# Store the group names ('g')
self.groupnames = []
# A dictionary with OBJMaterial objects read from the MTL files
# Key is the material name
self.materials = {}
# Material stack. Contains 2-tuples (offset, OBJMaterial) where
# offset is the face index of the first face (since the last 'g'
# command that uses this material)
# The stack is cleared (the last element remains) whenever the 'g'
# command occurs.
self.materialstack = [(0, OBJMaterial())]
# The collected faces
self.faces = []
# Flag that indicates if self.faces only contains triangles
self.trimesh_flag = True
# Was it already reported that points aren't supported?
self.point_msg_flag = False
# Was it already reported that lines aren't supported?
self.line_msg_flag = False
def end(self):
# Trigger the creation of the last object
self.g("default")
# mtllib
def mtllib(self, *files):
mtlreader = _MTLReader()
# Iterate over all MTL file names
for fname in files:
# Read the MTL file...
try:
f = file(fname)
except IOError, e:
print e
continue
mtlreader.read(f)
f.close()
# ...and keep the materials that don't exist yet
for mat in mtlreader.materials:
if mat.name not in self.materials:
self.materials[mat.name] = mat
# usemtl
def usemtl(self, name):
if name not in self.materials:
print >>sys.stderr, 'WARNING: No definition found for material "%s"'%name
# Store None so that the warning message will only get printed
# once for each missing material name
self.materials[name] = None
# Put the material on the stack (replace the top material if it has
# the same offset (i.e. it is unused))
offset = len(self.faces)
mat = self.materials[name]
if self.materialstack[-1][0]==offset:
self.materialstack.pop()
self.materialstack.append((len(self.faces), self.materials[name]))
# v
def v(self, vert):
"""Vertex."""
v = vec3(vert.x, vert.y, vert.z)
try:
v /= vert.w
except:
pass
self.verts.append(v)
# vn
def vn(self, n):
"""Normal."""
self.normals.append(n)
# vt
def vt(self, tv):
"""Texture vertex."""
self.tverts.append(tv)
# p
def p(self, *verts):
"""Points."""
if self.point_msg_flag:
return
print "OBJ import: Points are not supported"
self.point_msg_flag = True
# l
def l(self, *verts):
"""Line."""
if self.line_msg_flag:
return
print "OBJ import: Lines are not supported"
self.line_msg_flag = True
# f
def f(self, *verts):
"""Face."""
# ve = []
# for v,tv,n in verts:
# ve.append((v,None,None))
self.faces.append(verts)
# self.faces.append(ve)
if len(verts)!=3:
self.trimesh_flag = False
# g
def g(self, *groups):
"""Grouping info.
"""
if len(self.faces)!=0:
parent, names, node = self.findParent(self.groupnames)
name = "_".join(names)
if name=="":
name = "Mesh"
if self.trimesh_flag:
obj = self.createTriMesh(parent=parent, name=name)
else:
obj = self.createPolyhedron(parent=parent, name=name)
self.updateHierarchy(node, obj)
self.faces = []
self.trimesh_flag = True
self.groupnames = groups
# Clear the stack (the last material remains)
self.materialstack = [(0, self.materialstack[-1][1])]
# createTriMesh
def createTriMesh(self, parent=None, name=None):
"""Create a triangle mesh from the current set of faces.
This method may only be called if the faces really have only
3 vertices.
Returns the TriMesh object.
"""
# Build lookup tables (so that only the verts that are really
# required are stored in the TriMesh)
#
# Key: Original vertex index - Value: New vertex index
vert_lut = {}
has_normals = True
has_tverts = True
iv = 0
for f in self.faces:
for v,tv,n in f:
if v not in vert_lut:
vert_lut[v] = iv
iv+=1
if tv==None:
has_tverts = False
if n==None:
has_normals = False
numfaces = len(self.faces)
numverts = len(vert_lut)
tm = TriMeshGeom()
tm.verts.resize(numverts)
tm.faces.resize(numfaces)
# Set vertices
for v in vert_lut:
newidx = vert_lut[v]
tm.verts[newidx] = self.verts[v]
# Set faces
idx = 0
for i,f in enumerate(self.faces):
fi = []
for v,tv,n in f:
fi.append(vert_lut[v])
tm.faces[i] = fi
# Create variable N for storing the normals
if has_normals:
tm.newVariable("N", FACEVARYING, NORMAL)
N = tm.slot("N")
idx = 0
for f in self.faces:
for v,tv,n in f:
N[idx] = self.normals[n]
idx += 1
# Set texture vertices
if has_tverts:
tm.newVariable("st", FACEVARYING, FLOAT, 2)
st = tm.slot("st")
idx = 0
for f in self.faces:
for v,tv,n in f:
st[idx] = self.tverts[tv]
idx += 1
obj = TriMesh(name=name, parent=parent)
obj.geom = tm
# Set the materials
self.initMaterial(obj)
return obj
# createPolyhedron
def createPolyhedron(self, parent=None, name=None):
"""Create a polyhedron from the current set of faces.
Returns the Polyhedron object.
"""
# Build lookup tables (so that only the verts that are really
# required are stored in the Polyhedron)
#
# Key: Original vertex index - Value: New vertex index
vert_lut = {}
has_normals = True
has_tverts = True
iv = 0
for f in self.faces:
for v,tv,n in f:
if v not in vert_lut:
vert_lut[v] = iv
iv+=1
if tv==None:
has_tverts = False
if n==None:
has_normals = False
numpolys = len(self.faces)
numverts = len(vert_lut)
pg = PolyhedronGeom()
pg.verts.resize(numverts)
pg.setNumPolys(numpolys)
# Set vertices
for v in vert_lut:
newidx = vert_lut[v]
pg.verts[newidx] = self.verts[v]
# Set polys (this has to be done *before* any FACEVARYING variable
# is created, otherwise the size of the variable wouldn't be known)
idx = 0
for i,f in enumerate(self.faces):
loop = []
for v,tv,n in f:
loop.append(vert_lut[v])
pg.setPoly(i,[loop])
# Create variable N for storing the normals
if has_normals:
pg.newVariable("N", FACEVARYING, NORMAL)
N = pg.slot("N")
idx = 0
for f in self.faces:
for v,tv,n in f:
N[idx] = self.normals[n]
idx += 1
# Set texture vertices
if has_tverts:
pg.newVariable("st", FACEVARYING, FLOAT, 2)
st = pg.slot("st")
idx = 0
for f in self.faces:
for v,tv,n in f:
st[idx] = self.tverts[tv]
idx += 1
obj = Polyhedron(name=name, parent=parent)
obj.geom = pg
# Set the materials
self.initMaterial(obj)
return obj
# initMaterial
def initMaterial(self, obj):
"""Set the material(s) and the matid primitive variable.
"""
# Set the materials
nummats = len(self.materialstack)
obj.setNumMaterials(nummats)
for i, (offset, mat) in enumerate(self.materialstack):
obj.setMaterial(mat, i)
# Create a variable "matid" if there's more than 1 material...
if nummats>1:
obj.geom.newVariable("matid", UNIFORM, INT)
matid = obj.geom.slot("matid")
for id in range(len(self.materialstack)):
begin = self.materialstack[id][0]
if id+1<len(self.materialstack):
end = self.materialstack[id+1][0]
else:
end = matid.size()
for j in range(begin, end):
matid[j] = id
# findParent
def findParent(self, groupnames):
"""Find out who is the parent and what is the name of an object.
groupnames is the value of the 'g' tag. The method assumes that
one of the elements is the name of the object and the other are
parents. It's just that it is not known what is what.
The method tests each name if it matches a previously created
object at the current level and tries to traverse to the point
where the object has to be inserted. If everything went fine,
there should be one name left in the end and that's the name
of the object.
The methos returns a 3-tuple (parent, names, Hierarchy_node)
where parent is the parent WorldObject (or None), names is a
list of unprocessed names (ideally this should contain 1 element
which is the object's name) and Hierarchy_node is an internal
data item that is later used in the updateHierarchy() method).
"""
names = list(groupnames)
node = self.hierarchy
while 1:
for name in names:
# Is 'name' the name of a previously created object at the
# current hierarchy level?
if name in node[1]:
node = node[1][name]
names.remove(name)
# Restart search at the next hierarchy level
break
else:
# None of the names was found at the current hierarchy level
return node[0], names, node
def updateHierarchy(self, node, obj):
"""Update the local hierarchy after a WorldObject was created.
node is the 3rd element from the tuple returned by findParent().
obj is the created WorldObject.
"""
node[1][obj.name] = (obj, {})
# OBJImporter
class OBJImporter:
_protocols = ["Import"]
# extension
def extension():
"""Return the file extensions for this format."""
return ["obj"]
extension = staticmethod(extension)
# description
def description(self):
"""Return a short description for the file dialog."""
return "Wavefront object file"
description = staticmethod(description)
# importFile
def importFile(self, filename, parent=None):
"""Import an OBJ file."""
f = file(filename)
reader = _OBJReader(root=parent)
reader.read(f)
######################################################################
# Register the ObjImporter class as a plugin class
pluginmanager.register(OBJImporter)
|