# -*- indent-tabs-mode: t -*- #!/usr/bin/env python # -*- coding: utf-8 -*- # 3DS Import # Copyright (C) 2004 Thomas Paviot, 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 """3ds2soya This script is a 3DS exporter for Soya. """ import soya, soya.sdlconst import sys, struct, string, types from types import * import os ###################################################### # Data Structures ###################################################### #Some of the chunks that we will see #----- Primary Chunk, at the beginning of each file PRIMARY=19789 #0x4D4D #------ Mtextureain Chunks OBJECTINFO=15677 #0x3D3D // This gives the version of the mesh and is found right before the material and object information VERSION=2 #0x0002 // This gives the version of the .3ds file EDITKEYFRAME=45056 #0xB000 // 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 MATNAME=40960 #0xA000 // This holds the material name MATAMBIENT=40976 #0xA010 MATDIFFUSE=40992 #0xA020 // This holds the color of the object/material MATSPECULAR=41008 #0xA030 MATMAP=41472 #0xA200 // This is a header for a new material MATMAPFILE=41728 #0xA300 // This holds the file name of the texture OBJECT_MESH=16640 #0x4100 // This lets us know that we are reading a new object #------ sub defines of OBJECT_MESH OBJECT_VERTICES=16656 #0x4110 // The objects vertices OBJECT_FACES=16672 #0x4120 // The objects faces OBJECT_MATERIAL=16688 #0x4130 // This is found if the object has a material, either texture map or color OBJECT_UV=16704 #0x4140 // The UV texture coordinates OBJECT_TRANS_MATRIX=16736 #0x4160 // The translation matrix of the object 54 bytes ############################################################ # Mesh and Materialclass definition ############################################################ materials=[] #array of materials meshes=[] #array of meshes mat_index=0 class Mesh: def __init__(self): self.world=soya.World() self.verts=[] self.faces=[] self.materials=[] self.name="" class Material(soya.Material): def __init__(self): self.name="" def setName(self,name): self.name=name class chunk: ID=0 length=0 bytes_read=0 binary_format="=2): print "Cannot assign more than 2 materials to a mesh: Continue?%t|OK" break; else: material_found=1 for mat_index,material in enumerate(materials): if material.name == material_name: break break else: material_found=0 if(material_found==1): #read the number of faces using this material temp_data=file.read(struct.calcsize("H")) data=struct.unpack("H", temp_data) new_chunk.bytes_read+=2 num_faces_using_mat=data[0] for face_counter in range(0,num_faces_using_mat): temp_data=file.read(struct.calcsize("H")) new_chunk.bytes_read+=2 data=struct.unpack("H", temp_data) mesh.faces[data[0]].material=materials[mat_index] else: 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 elif (new_chunk.ID==OBJECT_UV): temp_data=file.read(struct.calcsize("H")) data=struct.unpack("H", temp_data) new_chunk.bytes_read+=2 num_uv=data[0] for counter in range(0,num_uv): temp_data=file.read(struct.calcsize("2f")) new_chunk.bytes_read+=8 #2 float x 4 bytes each data=struct.unpack("2f", temp_data) mesh.verts[counter].tex_x=data[0] mesh.verts[counter].tex_y=data[1] else: 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 previous_chunk.bytes_read+=new_chunk.bytes_read def process_next_material_chunk(file, previous_chunk, mat): new_chunk=chunk() temp_chunk=chunk() while (previous_chunk.bytes_read3): print "Non-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" #print "object info: lenght: ", new_chunk.length #recursively go through the rest of the file process_next_chunk(file, 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): mesh=Mesh() mesh.name=str(read_string(file)) meshes.append(mesh) #plus one for the null character that gets removed new_chunk.bytes_read+=(len(mesh.name)+1) process_next_object_chunk(file, new_chunk, mesh) #is it a material chunk? elif (new_chunk.ID==MATERIAL): #print "found a MATERIAL chunk" material=Material()#soya.Material()#Material.New() process_next_material_chunk(file, new_chunk, material) materials.append(material) 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 def load_3ds (filename): current_chunk=chunk() file=open(filename,"rb") read_chunk(file, current_chunk) if (current_chunk.ID!=PRIMARY): print "Fatal Error: Not a valid 3ds file: ", filename Exit() process_next_chunk(file, current_chunk) file.close() if len(meshes)==1: return meshes[0].world else: _3ds_world=soya.World() for elem in meshes: _3ds_world.add(elem.world) return _3ds_world def makeWorld(Name): data_f=os.path.join(soya.path[0],'worlds',Name+'.data') _3ds_f=os.path.join(soya.path[0],'3ds',Name+'.3ds') haveToDoIt=False if os.path.exists(data_f): if os.path.exists(_3ds_f): if os.path.getmtime(_3ds_f)>os.path.getmtime(data_f): haveToDoIt=True else: haveToDoIt=True if haveToDoIt: print "* Soya * Compiling 3ds file "+_3ds_f+" to Soya world..." o=load_3ds(_3ds_f) o.filename=Name o.save() def getObj(Name): """getObj(NAME) -> World Imports a "{name}.3ds" file (from the "3ds" directory) and converts it into a Soya World.""" makeWorld(Name) return soya.World.get(Name) def getModel(Name): """getModel(NAME) -> World Imports a "{name}.3ds" file (from the "3ds" directory) and converts it into a Soya Model.""" makeWorld(Name) return soya.Model.get(Name) if __name__=="__main__": soya.path.append(os.path.join(os.path.dirname(sys.argv[0]))) try: _3ds_file=sys.argv[1] world=getObj(_3ds_file) except: print "Usage:\npython 3ds2soya.py yourfile\nWithout .3ds extension" sys.exit() camera=soya.Camera(world) camera.set_xyz(0.0, 0.0, 3.0) light = soya.Light(world) light.set_xyz(0.5, 1.0, 2.0) soya.set_root_widget(camera) soya.init() soya.MainLoop(world).main_loop()