3ds importer distributed with Blender 2.41 is broken in 2 places so bugfixed and updated...

Version 0.93
- Tested with 400 3ds files from turbosquid, model cd's and net samples.
- Tactfully ignore faces that used the same verts twice.
- Rollback to 0.83 sloppy un-reorganized code, new code broke UV coord loading.
- Converted from NMesh to Mesh. First importer to use Mesh.
- Faster and cleaner new names.
- Use external comprehensive image loader.
- Re intergrated 0.92 and 0.9 changes
- Fixes for 2.41 compat.
- Non textured faces do not use a texture flag.
This commit is contained in:
Campbell Barton
2006-02-01 10:52:54 +00:00
parent c8ff92d965
commit 88e0f089be

View File

@@ -1,15 +1,14 @@
#!BPY
"""
Name: '3D Studio (.3ds)...'
Blender: 237
Blender: 241
Group: 'Import'
Tooltip: 'Import from 3DS file format. (.3ds)'
Tooltip: 'Import from 3DS file format (.3ds)'
"""
__author__ = ["Bob Holcomb", "Richard L<>rk<72>ng", "Damien McGinnes", "Campbell Barton"]
__url__ = ("blender", "elysiun", "http://www.gametutorials.com")
__version__ = "0.92"
__version__ = "0.93"
__bpydoc__ = """\
3ds Importer
@@ -18,7 +17,17 @@ This script imports a 3ds file and the materials into Blender for editing.
Loader is based on 3ds loader from www.gametutorials.com (Thanks DigiBen).
Changes:
0.93 by Campbell Barton<br>
- Tested with 400 3ds files from turbosquid and samples.
- Tactfully ignore faces that used the same verts twice.
- Rollback to 0.83 sloppy un-reorganized code, this broke UV coord loading.
- Converted from NMesh to Mesh.
- Faster and cleaner new names.
- Use external comprehensive image loader.
- Re intergrated 0.92 and 0.9 changes
- Fixes for 2.41 compat.
- Non textured faces do not use a texture flag.
0.92<br>
- Added support for diffuse, alpha, spec, bump maps in a single material
@@ -28,91 +37,87 @@ Changes:
- Use of Matrix() to copy matrix data<br>
- added support for material transparency<br>
0.81a (fork- not 0.9) Campbell Barton 2005-06-08<br>
- Simplified import code<br>
- Never overwrite data<br>
- Faster list handling<br>
- Leaves import selected<br>
0.83 2005-08-07: Campell Barton
- Aggressive image finding and case insensitivy for posisx systems.
0.81 Damien McGinnes 2005-01-09<br>
- handle missing images better<br>
0.82a 2005-07-22
- image texture loading (both for face uv and renderer)
0.8 Damien McGinnes 2005-01-08<br>
- copies sticky UV coords to face ones<br>
- handles images better<br>
0.82 - image texture loading (for face uv)
0.81a (fork- not 0.9) Campbell Barton 2005-06-08
- Simplified import code
- Never overwrite data
- Faster list handling
- Leaves import selected
0.81 Damien McGinnes 2005-01-09
- handle missing images better
0.8 Damien McGinnes 2005-01-08
- copies sticky UV coords to face ones
- handles images better
- Recommend that you run 'RemoveDoubles' on each imported mesh after using this script
"""
# $Id$
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) Bob Holcomb
#
# 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.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------
# Importing modules
import Blender
from Blender import NMesh, Scene, Object, Material, Image, Texture
from Blender import Mesh, Scene, Object, Material, Image, Texture, Lamp, Mathutils
from Blender.Mathutils import Vector
import BPyImage
reload(BPyImage)
import sys, struct, string
import struct
import os
#this script imports uvcoords as sticky vertex coords
#this parameter enables copying these to face uv coords
#which shold be more useful.
#===========================================================================#
# Returns unique name of object/mesh (stops overwriting existing meshes) #
#===========================================================================#
def getUniqueName(name):
count = 0
newname = name[:19]
while newname in getUniqueName.uniqueObNames:
newname = '%s.%.3i' % (name[:15], count)
count+=1
# Dont use again.
getUniqueName.uniqueObNames.append(newname)
return newname
getUniqueName.uniqueObNames = Blender.NMesh.GetNames() + [ob.name for ob in Object.Get()]
def createBlenderTexture(material, name, image):
texture = Texture.New(name)
texture.setType('Image')
texture.image = image
material.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
######################################################
# Data Structures
######################################################
#----- Primary Chunk,
PRIMARY = long("0x4D4D",16) # should be aat the beginning of each file
VERSION = long("0x0002",16) #This gives the version of the .3ds file
EDITOR_BLOCK = long("0x3D3D",16) #this is the Editor Data block, contains objects, materials
KEYFRAME_BLOCK = long("0xB000",16) #This is the header for all of the key frame info
#------ sub defines of EDITOR_BLOCK
MATERIAL_BLOCK = long("0xAFFF",16) #This stores the Material info
OBJECT_BLOCK = long("0x4000",16) #This stores the Object,Camera,Light
#Some of the chunks that we will see
#----- Primary Chunk, at the beginning of each file
PRIMARY= long("0x4D4D",16)
#------ sub defines of OBJECT_BLOCK
OBJECT_MESH = long("0x4100",16) # This lets us know that we are reading a new object
OBJECT_LIGHT = long("0x4600",16) # This lets un know we are reading a light object
OBJECT_CAMERA = long("0x4700",16) # This lets un know we are reading a camera object
#------ sub defines of OBJECT_MESH
MESH_VERTICES = long("0x4110",16) # The objects vertices
MESH_FACES = long("0x4120",16) # The objects faces
MESH_MATERIAL = long("0x4130",16) # This is found if the object has a material, either texture map or color
MESH_UV = long("0x4140",16) # The UV texture coordinates
MESH_TRANS_MATRIX = long("0x4160",16) # The Object Matrix
MESH_COLOR = long("0x4165",16) # The color of the object
MESH_TEXTURE_INFO = long("0x470",16) # Info about the Object Texture
#------ sub defines of OBJECT_CAMERA
CAMERA_CONE = long("0x4710",16) # The camera see cone
CAMERA_RANGES = long("0x4720",16) # The camera range values
#------ sub defines of OBJECT_LIGHT
LIGHT_SPOTLIGHT = long("0x4610",16) # A spotlight
LIGHT_ATTENUATE = long("0x4625",16) # Light attenuation values
#------ Main Chunks
OBJECTINFO = long("0x3D3D",16); #This gives the version of the mesh and is found right before the material and object information
VERSION = long("0x0002",16); #This gives the version of the .3ds file
EDITKEYFRAME= long("0xB000",16); #This is the header for all of the key frame info
#------ sub defines of OBJECTINFO
MATERIAL=45055 #0xAFFF // This stored the texture info
OBJECT=16384 #0x4000 // This stores the faces, vertices, etc...
#>------ sub defines of MATERIAL
#------ sub defines of MATERIAL_BLOCK
MAT_NAME = long("0xA000",16) # This holds the material name
MAT_AMBIENT = long("0xA010",16) # Ambient color of the object/material
@@ -128,24 +133,56 @@ MAT_SPECULAR_MAP= long("0xA204",16) # This is a header for a new specular map
MAT_OPACITY_MAP = long("0xA210",16) # This is a header for a new opacity map
MAT_REFLECTION_MAP= long("0xA220",16) # This is a header for a new reflection map
MAT_BUMP_MAP = long("0xA230",16) # This is a header for a new bump map
MAT_MAP_FILENAME= long("0xA300",16) # This holds the file name of the texture
#lots more to add here for maps
MAT_MAP_FILENAME = long("0xA300",16); # This holds the file name of the texture
######################################################
# Globals
######################################################
TEXTURE_DICT={}
MATERIAL_DICT={}
#>------ sub defines of OBJECT
OBJECT_MESH = long("0x4100",16); # This lets us know that we are reading a new object
OBJECT_LAMP = long("0x4600",16); # This lets un know we are reading a light object
OBJECT_LAMP_SPOT = long("0x4610",16); # The light is a spotloght.
OBJECT_LAMP_OFF = long("0x4620",16); # The light off.
OBJECT_LAMP_ATTENUATE = long("0x4625",16);
OBJECT_LAMP_RAYSHADE = long("0x4627",16);
OBJECT_LAMP_SHADOWED = long("0x4630",16);
OBJECT_LAMP_LOCAL_SHADOW = long("0x4640",16);
OBJECT_LAMP_LOCAL_SHADOW2 = long("0x4641",16);
OBJECT_LAMP_SEE_CONE = long("0x4650",16);
OBJECT_LAMP_SPOT_RECTANGULAR= long("0x4651",16);
OBJECT_LAMP_SPOT_OVERSHOOT= long("0x4652",16);
OBJECT_LAMP_SPOT_PROJECTOR= long("0x4653",16);
OBJECT_LAMP_EXCLUDE= long("0x4654",16);
OBJECT_LAMP_RANGE= long("0x4655",16);
OBJECT_LAMP_ROLL= long("0x4656",16);
OBJECT_LAMP_SPOT_ASPECT= long("0x4657",16);
OBJECT_LAMP_RAY_BIAS= long("0x4658",16);
OBJECT_LAMP_INNER_RANGE= long("0x4659",16);
OBJECT_LAMP_OUTER_RANGE= long("0x465A",16);
OBJECT_LAMP_MULTIPLIER = long("0x465B",16);
OBJECT_LAMP_AMBIENT_LIGHT = long("0x4680",16);
######################################################
# Chunk Class
######################################################
OBJECT_CAMERA= long("0x4700",16); # This lets un know we are reading a camera object
#>------ sub defines of CAMERA
OBJECT_CAM_RANGES= long("0x4720",16); # The camera range values
#>------ sub defines of OBJECT_MESH
OBJECT_VERTICES = long("0x4110",16); # The objects vertices
OBJECT_FACES = long("0x4120",16); # The objects faces
OBJECT_MATERIAL = long("0x4130",16); # This is found if the object has a material, either texture map or color
OBJECT_UV = long("0x4140",16); # The UV texture coordinates
OBJECT_TRANS_MATRIX = long("0x4160",16); # The Object Matrix
global scn
scn = None
#the chunk class
class chunk:
ID=0
length=0
bytes_read=0
#we don't read in the bytes_read, we compute that
binary_format="<HI"
def __init__(self):
@@ -154,20 +191,53 @@ class chunk:
self.bytes_read=0
def dump(self):
print "ID: ", self.ID
print "ID in hex: ", hex(self.ID)
print "length: ", self.length
print "bytes_read: ", self.bytes_read
######################################################
# Helper functions
######################################################
def read_chunk(file, chunk):
temp_data=file.read(struct.calcsize(chunk.binary_format))
data=struct.unpack(chunk.binary_format, temp_data)
chunk.ID=data[0]
chunk.length=data[1]
chunk.bytes_read=6
temp_data=file.read(struct.calcsize(chunk.binary_format))
data=struct.unpack(chunk.binary_format, temp_data)
chunk.ID=data[0]
chunk.length=data[1]
#update the bytes read function
chunk.bytes_read=6
#if debugging
#chunk.dump()
def read_string(file):
s=""
index=0
#print "reading a string"
#read in the characters till we get a null character
temp_data=file.read(1)
data=struct.unpack("c", temp_data)
s=s+(data[0])
#print "string: ",s
while(ord(s[index])!=0):
index+=1
temp_data=file.read(1)
data=struct.unpack("c", temp_data)
s=s+(data[0])
#print "string: ",s
#remove the null character from the string
the_string=s[:-1]
return the_string
######################################################
# IMPORT
######################################################
def process_next_object_chunk(file, previous_chunk):
new_chunk=chunk()
temp_chunk=chunk()
while (previous_chunk.bytes_read<previous_chunk.length):
#read the next chunk
read_chunk(file, new_chunk)
def skip_to_end(file, skip_chunk):
buffer_size=skip_chunk.length-skip_chunk.bytes_read
@@ -175,37 +245,6 @@ def skip_to_end(file, skip_chunk):
temp_data=file.read(struct.calcsize(binary_format))
skip_chunk.bytes_read+=buffer_size
def read_string(file):
s=""
index=0
#read the first character
temp_data=file.read(1)
data=struct.unpack("c", temp_data)
s=s+(data[0])
#read in the characters till we get a null character
while(ord(s[index])!=0):
index+=1
temp_data=file.read(1)
data=struct.unpack("c", temp_data)
s=s+(data[0])
the_string=s[:-1] #remove the null character from the string
return str(the_string)
def getUniqueName(name):
newName = name
uniqueInt = 0
while 1:
try:
ob = Object.Get(newName)
# Okay, this is working, so lets make a new name
newName = '%s.%d' % (name, uniqueInt)
uniqueInt +=1
except AttributeError:
if newName not in NMesh.GetNames():
return newName
else:
newName = '%s.%d' % (name, uniqueInt)
uniqueInt +=1
def add_texture_to_material(image, texture, material, mapto):
if mapto=="DIFFUSE":
@@ -220,226 +259,155 @@ def add_texture_to_material(image, texture, material, mapto):
print "/tError: Cannot map to ", mapto
return
texture.setImage(image)
texture_list=material.getTextures()
index=0
for tex in texture_list:
if tex==None:
material.setTexture(index,texture,Texture.TexCo.OBJECT,map)
material.setTexture(index,texture,Texture.TexCo.UV,map)
return
else:
index+=1
if index>10:
print "/tError: Cannot add diffuse map. Too many textures"
def process_next_chunk(file, filename, previous_chunk):
scn = Scene.GetCurrent()
contextObName = None
contextLamp = [None, None] # object, Data
contextMaterial = None
contextMatrix = Blender.Mathutils.Matrix(); contextMatrix.identity()
contextMesh = None
TEXTURE_DICT={}
MATDICT={}
TEXMODE = Mesh.FaceModes['TEX']
objectList = [] # Keep a list of imported objects.
######################################################
# Process an object (tri-mesh, Camera, or Light)
######################################################
def process_object_block(file, previous_chunk, object_list):
# Localspace variable names, faster.
STRUCT_SIZE_1CHAR = struct.calcsize("c")
STRUCT_SIZE_2FLOAT = struct.calcsize("2f")
STRUCT_SIZE_3FLOAT = struct.calcsize("3f")
STRUCT_SIZE_UNSIGNED_SHORT = struct.calcsize("H")
STRUCT_SIZE_4UNSIGNED_SHORT = struct.calcsize("4H")
STRUCT_SIZE_4x3MAT = struct.calcsize("ffffffffffff")
#spare chunks
def putContextMesh(myContextMesh):
INV_MAT = Blender.Mathutils.Matrix(contextMatrix)
INV_MAT.invert()
contextMesh.transform(INV_MAT)
# Faces without an image can have the image flag disabled.
if myContextMesh.faceUV:
TEX_ON_FLAG = Mesh.FaceModes['TEX']
for f in myContextMesh.faces:
if not f.image:
f.mode &= ~TEX_ON_FLAG # disable tex flag.
for c in f.col:
c.b=c.g=c.r=255
newOb = Object.New('Mesh', contextMesh.name) # Meshes name is always a free object name too.
newOb.link(contextMesh)
scn.link(newOb)
newOb.Layers = scn.Layers
newOb.sel = 1
objectList.append(newOb) # last 2 recal normals
newOb.setMatrix(contextMatrix)
Blender.Window.EditMode(1)
Blender.Window.EditMode(0)
#a spare chunk
new_chunk=chunk()
temp_chunk=chunk()
global TEXURE_DICT
global MATERIAL_DICT
#don't know which one we're making, so let's have a place for one of each
new_mesh=None
new_light=None
new_camera=None
#all objects have a name (first thing)
tempName = str(read_string(file))
obj_name = getUniqueName( tempName )
previous_chunk.bytes_read += (len(tempName)+1)
while (previous_chunk.bytes_read<previous_chunk.length):
read_chunk(file, new_chunk)
if (new_chunk.ID==OBJECT_MESH):
new_mesh=Blender.NMesh.New(obj_name)
while (new_chunk.bytes_read<new_chunk.length):
read_chunk(file, temp_chunk)
if (temp_chunk.ID==MESH_VERTICES):
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
temp_chunk.bytes_read+=2
num_verts=data[0]
for counter in range (num_verts):
temp_data=file.read(STRUCT_SIZE_3FLOAT)
temp_chunk.bytes_read += STRUCT_SIZE_3FLOAT
data=struct.unpack("3f", temp_data)
v=NMesh.Vert(data[0],data[1],data[2])
new_mesh.verts.append(v)
elif (temp_chunk.ID==MESH_FACES):
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
temp_chunk.bytes_read+=2
num_faces=data[0]
for counter in range(num_faces):
temp_data=file.read(STRUCT_SIZE_4UNSIGNED_SHORT)
temp_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each
data=struct.unpack("4H", temp_data)
#insert the mesh info into the faces, don't worry about data[3] it is a 3D studio thing
f = NMesh.Face( [new_mesh.verts[data[i]] for i in xrange(3)] )
f.uv = [ tuple(new_mesh.verts[data[i]].uvco[:2]) for i in xrange(3) ]
new_mesh.faces.append(f)
elif (temp_chunk.ID==MESH_MATERIAL):
material_name=""
material_name=str(read_string(file))
temp_chunk.bytes_read += len(material_name)+1 # remove 1 null character.
material_found=0
for mat in Material.Get():
if(mat.name==material_name):
if len(new_mesh.materials) >= 15:
print "\tCant assign more than 16 materials per mesh, keep going..."
break
else:
meshHasMat = 0
for myMat in new_mesh.materials:
if myMat.name == mat.name:
meshHasMat = 1
if meshHasMat == 0:
new_mesh.addMaterial(mat)
material_found=1
#figure out what material index this is for the mesh
for mat_counter in range(len(new_mesh.materials)):
if new_mesh.materials[mat_counter].name == material_name:
mat_index=mat_counter
break # get out of this for loop so we don't accidentally set material_found back to 0
else:
material_found=0
if material_found == 1:
#read the number of faces using this material
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
temp_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
num_faces_using_mat=data[0]
#list of faces using mat
for face_counter in range(num_faces_using_mat):
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
temp_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
data=struct.unpack("H", temp_data)
new_mesh.faces[data[0]].materialIndex = mat_index
try:
mname = MATERIAL_DICT[mat.name]
new_mesh.faces[data[0]].image = TEXTURE_DICT[mname]
except:
continue
else:
#read past the information about the material you couldn't find
skip_to_end(file,temp_chunk)
elif (new_chunk.ID == MESH_UV):
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
temp_chunk.bytes_read+=2
num_uv=data[0]
for counter in range(num_uv):
temp_data=file.read(STRUCT_SIZE_2FLOAT)
temp_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each
data=struct.unpack("2f", temp_data)
#insert the insert the UV coords in the vertex data
new_mesh.verts[counter].uvco = data
elif (new_chunk.ID == MESH_TRANS_MATRIX):
temp_data=file.read(STRUCT_SIZE_4x3MAT)
data = list( struct.unpack("ffffffffffff", temp_data) )
temp_chunk.bytes_read += STRUCT_SIZE_4x3MAT
new_matrix = Blender.Mathutils.Matrix(\
data[:3] + [0],\
data[3:6] + [0],\
data[6:9] + [0],\
data[9:] + [1])
new_mesh.setMatrix(new_matrix)
else:
skip_to_end(file, temp_chunk)
new_chunk.bytes_read+=temp_chunk.bytes_read
elif (new_chunk.ID==OBJECT_LIGHT):
skip_to_end(file,new_chunk)
elif (new_chunk.ID==OBJECT_CAMERA):
skip_to_end(file,new_chunk)
else: #don't know what kind of object it is
skip_to_end(file,new_chunk)
if new_mesh!=None:
object_list.append(NMesh.PutRaw(new_mesh))
if new_light!=None:
object_list.append(new_light)
if new_camera!=None:
object_list.append(new_camera)
previous_chunk.bytes_read+=new_chunk.bytes_read
######################################################
# Process a Material
######################################################
def process_material_block(file, previous_chunk):
# Localspace variable names, faster.
STRUCT_SIZE_3BYTE = struct.calcsize("3B")
STRUCT_SIZE_UNSIGNED_SHORT = struct.calcsize("H")
#spare chunks
new_chunk=chunk()
temp_chunk=chunk()
global TEXURE_DICT
global MATERIAL_DICT
new_material=Blender.Material.New()
#loop through all the data for this chunk (previous chunk) and see what it is
while (previous_chunk.bytes_read<previous_chunk.length):
#read the next chunk
#print "reading a chunk"
read_chunk(file, new_chunk)
#is it a Version chunk?
if (new_chunk.ID==VERSION):
#print "found a VERSION chunk"
#read in the version of the file
#it's an unsigned short (H)
temp_data=file.read(struct.calcsize("I"))
data=struct.unpack("I", temp_data)
version=data[0]
new_chunk.bytes_read+=4 #read the 4 bytes for the version number
#this loader works with version 3 and below, but may not with 4 and above
if (version>3):
print "\tNon-Fatal Error: Version greater than 3, may not load correctly: ", version
#is it an object info chunk?
elif (new_chunk.ID==OBJECTINFO):
# print "found an OBJECTINFO chunk"
process_next_chunk(file, filename, new_chunk)
#keep track of how much we read in the main chunk
new_chunk.bytes_read+=temp_chunk.bytes_read
#is it an object chunk?
elif (new_chunk.ID==OBJECT):
tempName = read_string(file)
contextObName = getUniqueName( tempName )
new_chunk.bytes_read += (len(tempName)+1)
if (new_chunk.ID==MAT_NAME):
#is it a material chunk?
elif (new_chunk.ID==MATERIAL):
# print "found a MATERIAL chunk"
contextMaterial = Material.New()
elif (new_chunk.ID==MAT_NAME):
# print "Found a MATNAME chunk"
material_name=""
material_name=str(read_string(file))
new_chunk.bytes_read+=(len(material_name)+1) #plus one for the null character that ended the string
new_material.setName(material_name)
MATERIAL_DICT[material_name] = new_material.name
material_name=read_string(file)
#plus one for the null character that ended the string
new_chunk.bytes_read+=(len(material_name)+1)
contextMaterial.setName(material_name)
MATDICT[material_name] = contextMaterial.name
elif (new_chunk.ID==MAT_AMBIENT):
# print "Found a MATAMBIENT chunk"
read_chunk(file, temp_chunk)
temp_data=file.read(STRUCT_SIZE_3BYTE)
temp_data=file.read(struct.calcsize("3B"))
data=struct.unpack("3B", temp_data)
temp_chunk.bytes_read+=3
new_material.mirCol = [float(col)/255 for col in data] # data [0,1,2] == rgb
contextMaterial.mirCol = [float(col)/255 for col in data] # data [0,1,2] == rgb
new_chunk.bytes_read+=temp_chunk.bytes_read
elif (new_chunk.ID==MAT_DIFFUSE):
# print "Found a MATDIFFUSE chunk"
read_chunk(file, temp_chunk)
temp_data=file.read(STRUCT_SIZE_3BYTE)
temp_data=file.read(struct.calcsize("3B"))
data=struct.unpack("3B", temp_data)
temp_chunk.bytes_read+=3
new_material.rgbCol = [float(col)/255 for col in data] # data [0,1,2] == rgb
contextMaterial.rgbCol = [float(col)/255 for col in data] # data [0,1,2] == rgb
new_chunk.bytes_read+=temp_chunk.bytes_read
elif (new_chunk.ID==MAT_SPECULAR):
# print "Found a MATSPECULAR chunk"
read_chunk(file, temp_chunk)
temp_data=file.read(STRUCT_SIZE_3BYTE)
temp_data=file.read(struct.calcsize("3B"))
data=struct.unpack("3B", temp_data)
temp_chunk.bytes_read+=3
new_material.specCol = [float(col)/255 for col in data] # data [0,1,2] == rgb
contextMaterial.specCol = [float(col)/255 for col in data] # data [0,1,2] == rgb
new_chunk.bytes_read+=temp_chunk.bytes_read
elif (new_chunk.ID==MAT_TEXTURE_MAP):
#print 'DIFFUSE MAP'
new_texture=Blender.Texture.New('Diffuse')
new_texture.setType('Image')
while (new_chunk.bytes_read<new_chunk.length):
@@ -448,18 +416,7 @@ def process_material_block(file, previous_chunk):
if (temp_chunk.ID==MAT_MAP_FILENAME):
texture_name=""
texture_name=str(read_string(file))
try:
img = Image.Load(texture_name)
TEXTURE_DICT[new_material.name]=img
except IOError:
fname = os.path.join( os.path.dirname(FILENAME), texture_name)
try:
img = Image.Load(fname)
TEXTURE_DICT[new_material.name]=img
except IOError:
print "\tERROR: failed to load image ",texture_name
TEXTURE_DICT[new_material.name] = None # Dummy
img=Blender.Image.New(fname,1,1,24) #blank image
img = TEXTURE_DICT[contextMaterial.name] = BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
else:
@@ -468,8 +425,8 @@ def process_material_block(file, previous_chunk):
new_chunk.bytes_read+=temp_chunk.bytes_read
#add the map to the material in the right channel
add_texture_to_material(img, new_texture, new_material, "DIFFUSE")
add_texture_to_material(img, new_texture, contextMaterial, "DIFFUSE")
elif (new_chunk.ID==MAT_SPECULAR_MAP):
new_texture=Blender.Texture.New('Specular')
new_texture.setType('Image')
@@ -479,18 +436,7 @@ def process_material_block(file, previous_chunk):
if (temp_chunk.ID==MAT_MAP_FILENAME):
texture_name=""
texture_name=str(read_string(file))
try:
img = Image.Load(texture_name)
TEXTURE_DICT[new_material.name]=img
except IOError:
fname = os.path.join( os.path.dirname(FILENAME), texture_name)
try:
img = Image.Load(fname)
TEXTURE_DICT[new_material.name]=img
except IOError:
print "\tERROR: failed to load image ",texture_name
TEXTURE_DICT[new_material.name] = None # Dummy
img=Blender.Image.New(fname,1,1,24) #blank image
img = BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
else:
skip_to_end(file, temp_chunk)
@@ -498,7 +444,7 @@ def process_material_block(file, previous_chunk):
new_chunk.bytes_read+=temp_chunk.bytes_read
#add the map to the material in the right channel
add_texture_to_material(img, new_texture, new_material, "SPECULAR")
add_texture_to_material(img, new_texture, contextMaterial, "SPECULAR")
elif (new_chunk.ID==MAT_OPACITY_MAP):
new_texture=Blender.Texture.New('Opacity')
@@ -509,18 +455,7 @@ def process_material_block(file, previous_chunk):
if (temp_chunk.ID==MAT_MAP_FILENAME):
texture_name=""
texture_name=str(read_string(file))
try:
img = Image.Load(texture_name)
TEXTURE_DICT[new_material.name]=img
except IOError:
fname = os.path.join( os.path.dirname(FILENAME), texture_name)
try:
img = Image.Load(fname)
TEXTURE_DICT[new_material.name]=img
except IOError:
print "\tERROR: failed to load image ",texture_name
TEXTURE_DICT[new_material.name] = None # Dummy
img=Blender.Image.New(fname,1,1,24) #blank image
img = BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
else:
skip_to_end(file, temp_chunk)
@@ -528,7 +463,7 @@ def process_material_block(file, previous_chunk):
new_chunk.bytes_read+=temp_chunk.bytes_read
#add the map to the material in the right channel
add_texture_to_material(img, new_texture, new_material, "OPACITY")
add_texture_to_material(img, new_texture, contextMaterial, "OPACITY")
elif (new_chunk.ID==MAT_BUMP_MAP):
new_texture=Blender.Texture.New('Bump')
@@ -539,18 +474,7 @@ def process_material_block(file, previous_chunk):
if (temp_chunk.ID==MAT_MAP_FILENAME):
texture_name=""
texture_name=str(read_string(file))
try:
img = Image.Load(texture_name)
TEXTURE_DICT[new_material.name]=img
except IOError:
fname = os.path.join( os.path.dirname(FILENAME), texture_name)
try:
img = Image.Load(fname)
TEXTURE_DICT[new_material.name]=img
except IOError:
print "\tERROR: failed to load image ",texture_name
TEXTURE_DICT[new_material.name] = None # Dummy
img=Blender.Image.New(fname,1,1,24) #blank image
img = BPyImage.comprehensiveImageLoad(texture_name, FILENAME)
new_chunk.bytes_read += (len(texture_name)+1) #plus one for the null character that gets removed
else:
skip_to_end(file, temp_chunk)
@@ -558,105 +482,319 @@ def process_material_block(file, previous_chunk):
new_chunk.bytes_read+=temp_chunk.bytes_read
#add the map to the material in the right channel
add_texture_to_material(img, new_texture, new_material, "BUMP")
add_texture_to_material(img, new_texture, contextMaterial, "BUMP")
elif (new_chunk.ID==MAT_TRANSPARENCY):
read_chunk(file, temp_chunk)
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
temp_chunk.bytes_read+=2
new_material.setAlpha(1-(float(data[0])/100))
contextMaterial.setAlpha(1-(float(data[0])/100))
new_chunk.bytes_read+=temp_chunk.bytes_read
elif (new_chunk.ID==OBJECT_LAMP): # Basic lamp support.
# print "Found an OBJECT_MESH chunk"
#if contextLamp != None: # Write context mesh if we have one.
# putContextLamp(contextLamp)
else:
skip_to_end(file,new_chunk)
#print 'LAMP!!!!!!!!!'
temp_data=file.read(STRUCT_SIZE_3FLOAT)
data=struct.unpack("3f", temp_data)
new_chunk.bytes_read+=STRUCT_SIZE_3FLOAT
x,y,z =tuple(data)
contextLamp[0] = Object.New('Lamp')
contextLamp[1] = Lamp.New()
contextLamp[0].link(contextLamp[1])
scn.link(contextLamp[0])
#print "number of faces: ", num_faces
#print x,y,z
contextLamp[0].setLocation(x,y,z)
# Reset matrix
contextMatrix = Mathutils.Matrix(); contextMatrix.identity()
#print contextLamp.name, '"""""""'
elif (new_chunk.ID==OBJECT_MESH):
# print "Found an OBJECT_MESH chunk"
if contextMesh != None: # Write context mesh if we have one.
putContextMesh(contextMesh)
contextMesh = Mesh.New()
contextMeshUV = None
#contextMesh.vertexUV= 1 # Make sticky coords.
# Reset matrix
contextMatrix = Blender.Mathutils.Matrix(); contextMatrix.identity()
previous_chunk.bytes_read+=new_chunk.bytes_read
elif (new_chunk.ID==OBJECT_VERTICES):
# print "Found an OBJECT_VERTICES chunk"
#print "object_verts: length: ", new_chunk.length
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
new_chunk.bytes_read+=2
num_verts=data[0]
# print "number of verts: ", num_verts
def getvert():
temp_data=file.read(STRUCT_SIZE_3FLOAT)
new_chunk.bytes_read += STRUCT_SIZE_3FLOAT #12: 3 floats x 4 bytes each
data=struct.unpack("3f", temp_data)
return Vector(data[0],data[1],data[2])
contextMesh.verts.extend( [getvert() for i in xrange(num_verts)] )
#print "object verts: bytes read: ", new_chunk.bytes_read
######################################################
# process a main chunk
######################################################
def process_main_chunk(file,previous_chunk,new_object_list):
#spare chunks
new_chunk=chunk()
temp_chunk=chunk()
#Go through the main chunk
while (previous_chunk.bytes_read<previous_chunk.length):
read_chunk(file, new_chunk)
if (new_chunk.ID==VERSION):
temp_data=file.read(struct.calcsize("I"))
data=struct.unpack("I", temp_data)
version=data[0]
new_chunk.bytes_read+=4 #read the 4 bytes for the version number
if (version>3): #this loader works with version 3 and below, but may not with 4 and above
print "\tNon-Fatal Error: Version greater than 3, may not load correctly: ", version
elif (new_chunk.ID==EDITOR_BLOCK):
while(new_chunk.bytes_read<new_chunk.length):
read_chunk(file, temp_chunk)
if (temp_chunk.ID==MATERIAL_BLOCK):
process_material_block(file, temp_chunk)
elif (temp_chunk.ID==OBJECT_BLOCK):
process_object_block(file, temp_chunk,new_object_list)
elif (new_chunk.ID==OBJECT_FACES):
# print "Found an OBJECT_FACES chunk"
#print "object faces: length: ", new_chunk.length
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
new_chunk.bytes_read+=2
num_faces=data[0]
#print "number of faces: ", num_faces
"""
for counter in xrange(num_faces):
temp_data=file.read(STRUCT_SIZE_4UNSIGNED_SHORT)
new_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each
data=struct.unpack("4H", temp_data)
#insert the mesh info into the faces, don't worry about data[3] it is a 3D studio thing
contextMesh.faces.extend([contextMesh.verts[data[i]] for i in xrange(3) ])
f = contextMesh.faces[-1]
if contextMeshUV:
contextMesh.faceUV= 1 # Make sticky coords.
f.uv = [ contextMeshUV[data[i]] for i in xrange(3) ]
"""
def getface():
temp_data=file.read(STRUCT_SIZE_4UNSIGNED_SHORT)
new_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each
data=struct.unpack("4H", temp_data)
verts = [contextMesh.verts[data[i]] for i in xrange(3) ]
if verts[0]==verts[1] or verts[0]==verts[2] or verts[1]==verts[2]:
return None
else:
skip_to_end(file,temp_chunk)
return verts
contextFaceMapping = {} # So error faces dont unsync the this.
#contextMesh.faces.extend( [getface() for i in xrange(num_faces)] )
for i in xrange(num_faces):
ok=0
face= getface()
if face:
lenback = len(contextMesh.faces)
contextMesh.faces.extend( face )
if len(contextMesh.faces) != lenback:
ok=1
contextFaceMapping[i] = len(contextMesh.faces)-1
if not ok:
contextFaceMapping[i] = None
new_chunk.bytes_read+=temp_chunk.bytes_read
else:
skip_to_end(file,new_chunk)
previous_chunk.bytes_read+=new_chunk.bytes_read
#print 'LENFACEWS', len(contextMesh.faces), num_faces
if contextMeshUV:
contextMesh.faceUV= 1
for f in contextMesh.faces:
f.uv = [contextMeshUV[v.index] for v in f.v ]
#print "object faces: bytes read: ", new_chunk.bytes_read
#***********************************************
# main entry point for loading 3ds files
#***********************************************
def load_3ds (filename):
current_chunk=chunk()
print "--------------------------------"
print 'Importing "%s"' % filename
time1 = Blender.sys.time() #for timing purposes
file=open(filename,"rb")
new_object_list = []
elif (new_chunk.ID==OBJECT_MATERIAL):
# print "Found an OBJECT_MATERIAL chunk"
material_name=""
material_name = read_string(file)
new_chunk.bytes_read += len(material_name)+1 # remove 1 null character.
#look up the material in all the materials
material_found=0
for mat in Material.Get():
#found it, add it to the mesh
if(mat.name==material_name):
if len(contextMesh.materials) >= 15:
print "\tCant assign more than 16 materials per mesh, keep going..."
break
else:
meshHasMat = 0
for myMat in contextMesh.materials:
if myMat.name == mat.name:
meshHasMat = 1
if meshHasMat == 0:
contextMesh.materials = contextMesh.materials + [mat]
material_found=1
#figure out what material index this is for the mesh
for mat_counter in xrange(len(contextMesh.materials)):
if contextMesh.materials[mat_counter].name == material_name:
mat_index=mat_counter
#print "material index: ",mat_index
break # get out of this for loop so we don't accidentally set material_found back to 0
else:
material_found=0
# print "Not matching: ", mat.name, " and ", material_name
#print contextMesh.materials
if material_found == 1:
contextMaterial = mat
#read the number of faces using this material
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
num_faces_using_mat=data[0]
#list of faces using mat
for face_counter in xrange(num_faces_using_mat):
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
data=struct.unpack("H", temp_data)
facemap = contextFaceMapping[data[0]]
if facemap != None: # Face map can be None when teh face has bad data.
face = contextMesh.faces[facemap]
face.mat = mat_index
mname = MATDICT[contextMaterial.name]
try:
img = TEXTURE_DICT[mname]
except:
img = None
if img:
contextMesh.faceUV = 1
#print 'Assigning image', img.name
face.mode |= TEXMODE
face.image = img
else:
#read past the information about the material you couldn't find
#print "Couldn't find material. Reading past face material info"
buffer_size=new_chunk.length-new_chunk.bytes_read
binary_format=str(buffer_size)+"c"
temp_data=file.read(struct.calcsize(binary_format))
new_chunk.bytes_read+=buffer_size
#print "object mat: bytes read: ", new_chunk.bytes_read
elif (new_chunk.ID == OBJECT_UV):
# print "Found an OBJECT_UV chunk"
temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT)
data=struct.unpack("H", temp_data)
new_chunk.bytes_read+=2
num_uv=data[0]
def getuv():
temp_data=file.read(STRUCT_SIZE_2FLOAT)
new_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each
data=struct.unpack("2f", temp_data)
return Vector(data[0], data[1])
contextMeshUV = [ getuv() for i in xrange(num_uv) ]
'''
for counter in xrange(num_uv):
temp_data=file.read(STRUCT_SIZE_2FLOAT)
new_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each
data=struct.unpack("2f", temp_data)
#insert the insert the UV coords in the vertex data
contextMeshUV[counter].uvco = data
'''
elif (new_chunk.ID == OBJECT_TRANS_MATRIX):
# print "Found an OBJECT_TRANS_MATRIX chunk"
temp_data=file.read(STRUCT_SIZE_4x3MAT)
data = list( struct.unpack("ffffffffffff", temp_data) )
new_chunk.bytes_read += STRUCT_SIZE_4x3MAT
contextMatrix = Blender.Mathutils.Matrix(\
data[:3] + [0],\
data[3:6] + [0],\
data[6:9] + [0],\
data[9:] + [1])
else: #(new_chunk.ID!=VERSION or new_chunk.ID!=OBJECTINFO or new_chunk.ID!=OBJECT or new_chunk.ID!=MATERIAL):
# print "skipping to end of this chunk"
buffer_size=new_chunk.length-new_chunk.bytes_read
binary_format=str(buffer_size)+"c"
temp_data=file.read(struct.calcsize(binary_format))
new_chunk.bytes_read+=buffer_size
#update the previous chunk bytes read
previous_chunk.bytes_read += new_chunk.bytes_read
#print "Bytes left in this chunk: ", previous_chunk.length-previous_chunk.bytes_read
# FINISHED LOOP
# There will be a number of objects still not added
if contextMesh != None:
putContextMesh(contextMesh)
for ob in objectList:
ob.sel = 1
def load_3ds(filename):
print '\n\nImporting "%s"' % filename
#
for ob in Scene.GetCurrent().getChildren():
ob.sel = 0
time1 = Blender.sys.time()
global FILENAME
FILENAME=filename
current_chunk=chunk()
file=open(filename,"rb")
#here we go!
# print "reading the first chunk"
read_chunk(file, current_chunk)
if (current_chunk.ID!=PRIMARY):
print "\tFatal Error: Not a valid 3ds file: ", filename
file.close()
return
process_main_chunk(file, current_chunk, new_object_list)
process_next_chunk(file, filename, current_chunk)
# Select all new objects.
for ob in new_object_list: ob.sel = 1
print 'finished importing: "%s" in %.4f sec.' % (filename, (Blender.sys.time()-time1))
file.close()
#***********************************************
# MAIN
#***********************************************
def my_callback(filename):
load_3ds(filename)
Blender.Window.FileSelector(my_callback, "Import 3DS", '*.3ds')
if __name__ == '__main__':
Blender.Window.FileSelector(load_3ds, "Import 3DS", '*.3ds')
# For testing compatibility
'''
"""
TIME = Blender.sys.time()
import os
for _3ds in os.listdir('/3ds/'):
if _3ds.lower().endswith('3ds'):
print _3ds
newScn = Scene.New(_3ds)
newScn.makeCurrent()
my_callback('/3ds/' + _3ds)
os.system('find /metavr/ -iname "*.3ds" > /tmp/temp3ds_list')
file = open('/tmp/temp3ds_list', 'r')
lines = file.readlines()[200:]
file.close()
for i, _3ds in enumerate(lines):
_3ds= _3ds[:-1]
print "Importing", _3ds, i
_3ds_file = _3ds.split('/')[-1].split('\\')[-1]
newScn = Scene.New(_3ds_file)
newScn.makeCurrent()
my_callback(_3ds)
print "TOTAL TIME: ", Blender.sys.time() - TIME
'''
print "TOTAL TIME: %.6f" % (Blender.sys.time() - TIME)
"""