Dice3DS
Dice3DS is a set of Python modules for dealing with 3D Studio format files.
I have released it under the terms of a BSD-style license.
3D Studio is a 3D graphics modeling and rendering program that saved
it images in a rather simple binary file format known as 3DS format.
Although 3D Studio has not released the details of the 3DS format, it
has been reverse engineered by some ambitious people, and I used the
information to write Dice3DS, a Python package that slices and dices
3DS files.
Dice3DS requires Python 2.6,
and numpy 1.1.0. (Earlier
versions of Python, going back to 2.3, might still work but are no
longer officially supported.) The view3ds script additionally requires
any or all of PIL, PyOpenGL 3, PyGame, and Pyglet. PIL and PyOpenGL is enough
for most cases.
Documentation
There are two packages in Dice3DS: Dice3DS, and
Dice3DS.example. The latter includes some modules that
exemplify the use of Dice3DS, although they are not very
versatile.
- Module: Dice3DS.dom3ds
Slice and dice 3DS files.
Provides for reading, writing, and manipulating 3DS files. It's
called dom3ds because it's reminiscent of XML-DOM: it converts the 3DS
file into a hierarchy of objects, in much the same way XML-DOM
converts an XML file into a hierarchy of objects called the Document
Object Model. The dom3ds module creates an object for each chunk in
the 3DS file, which can be accessed hierarchially as attributes.
For example, once a 3DS file is loaded, you could access the smoothing
data of the second object like this:
dom.mdata.objects[2].ntri.faces.smoothing.array
- Function: read_3ds_file
Create a 3DS DOM from a file.
dom = read_3ds_file(filename,check_magic=True,tight=False,
recover=True)
filename: name of a 3DS file.
check_magic: If true, this function checks that the top level
chunk is the 3DS magic chunk (0x4D4D), and raises an exception
if it is not.
tight: Whether to use tighter error checking. Try disabling
if getting 3DS format errors.
recover: Whether to emit an Error chunk when an error is found;
otherwise raise an exception.
- Function: read_3ds_mem
Create a 3DS DOM from a memory buffer.
dom = read_3ds_mem(buffer,check_magic=True,tight=False,
recover=True)
buffer: is an image of the 3DS file in memory. It could be
a string, an mmaped file, or something else.
check_magic: If true, this function checks that the top level
chunk is the 3DS magic chunk (0x4D4D), and raises an exception
if it is not.
tight: Whether to use tighter error checking. Try disabling
if getting 3DS format errors.
recover: Whether to emit an Error chunk when an error is found;
otherwise raise an exception.
- Function: write_3ds_file
Write a 3DS file.
buf = write_3ds_file(filename,dom,check_magic=True)
filename: name of a 3DS file to write.
dom: the 3DS dom
check_magic: If true, this function checks that the top level
chunk is the 3DS magic chunk (0x4D4D), and raises an exception
if it is not.
- Function: write_3ds_mem
Output a 3DS DOM as a string.
buf = write_3ds_mem(dom,check_magic=True)
dom: the 3DS dom
check_magic: If true, this function checks that the top level
chunk is the 3DS magic chunk (0x4D4D), and raises an exception
if it is not.
- Function: dump_3ds_chunk
Dump a 3DS DOM to a file stream.
dump_3ds_chunk(filename,flo,arraylines=10,indent='')
chunk: The 3DS chunk to dump
flo: The file-like-object to dump output to (for example, sys.stdout)
arraylines: Max number of lines of array data to dump. If negative,
dump the whole array.
indent: Prefix string to all lines dumped; used to indent.
- Function: dump_3ds_file
Dump a text representation of 3DS DOM to a file stream.
dump_3ds_file(filename,flo,arraylines=2,tight=False,
recover=True)
filename: The 3DS file to dump
flo: The file-like-object to dump output to (for example, sys.stdout)
arraylines: Max number of lines of array data to dump. If negative,
dump the whole array.
tight: Whether to use tighter error checking. Try disabling
if getting 3DS format errors.
recover: Whether to emit an Error chunk when an error is found;
otherwise raise an exception.
- Function: remove_errant_chunks
Recursively remove any errant chunks.
remove_errant_chunks(chunk)
This recursively removes chunks that might prevent
successfully writing the DOM.
- Module: Dice3DS.util
Utitily function for Dice3DS.
Defines some routines for calculating normals and transforming points.
- Function: translate_points
Translate points in pointarray by the given matrix.
tpointarray = translate_points(pointarray,matrix)
Takes array of points and a homogenous (4D) transformation
matrix in exactly the same form in which they appear in the
3DS DOM.
Returns a pointarray with the points transformed by the matrix.
- Function: calculate_normals_no_smoothing
Calculate normals all perpendicular to the faces.
points,norms = calculate_normals_no_smoothing(
pointarray,facearray,smarray=None)
Takes an array of points and faces in exactly the same form in
which they appear in the 3DS DOM. It accepts a smoothing array,
but ignores it.
Returns a numpy.array of points, one per row, and a
numpy.array of the corresponding normals. The points are
returned as a list of consecutive triangles; the first three rows
make up the first triangle, the second three rows make up the
second triangle, and so on.
The normal vectors are determined by calculating the normal to
each face. There is no smoothing.
- Function: calculate_normals_by_cross_product
Calculate normals by smoothing, weighting by cross-product.
points,norms = calculate_normals_by_cross_product(
pointarray,facearray,smarray)
Takes an array of points, faces, and a smoothing group in exactly
the same form in which they appear in the 3DS DOM.
Returns a numpy.array of points, one per row, and a numpy.array of
the corresponding normals. The points are returned as a list of
consecutive triangles; the first three rows make up the first
triangle, the second three rows make up the second triangle, and
so on.
To calculate the normal of a given vertex on a given face, this
function averages the normal vector for all faces which have share
that vertex and a smoothing group.
The normals being averaged are weighted by the cross-product used
to obtain the face's normal, which is proportional to the area of
the face.
- Function: calculate_normals_by_angle_subtended
Calculate normals by smoothing, weighting by angle subtended.
points,norms = calculate_normals_by_angle_subtended(
pointarray,facearray,smarray)
Takes an array of points, faces, and a smoothing group in exactly
the same form in which they appear in the 3DS DOM.
Returns a numpy.array of points, one per row, and a numpy.array of
the corresponding normals. The points are returned as a list of
consecutive triangles; the first three rows make up the first
triangle, the second three rows make up the second triangle, and
so on.
To calculate the normal of a given vertex on a given face, this
function averages the normal vector for all faces which have share
that vertex, and a smoothing group.
The normals being averaged are weighted by the angle subtended.
- Module: Dice3DS.example.basicmodel
Basic abstract classes representing a 3DS model.
Defines some classes that represent objects and materials of a 3DS
file in a more convienient form. It has methods to convert from the
DOM format. The classes can serve as base classes for more advanced
uses.
- Class: BasicModel
Represents a basic model from a 3DS file.
This is an example of a more usable and direct representation
of a 3DS model. It can sometimes be hard to operate on data
while it's sitting in a 3DS DOM; this class and the related
classes BasicMesh and BasicMaterial make it more accessible.
- Class: BasicMesh
Represents a single mesh from a 3DS file.
This class, instances of which a BasicModel instance is
usually responsible for creating, takes a mesh data passed in
from a 3DS DOM, reduces the data somewhat, and calculates
normals.
mesh.name - name of the mesh
mesh.matarrays - list of materials and their corresponding
faces
mesh.points - numpy array of points, one row per
vertex, each set of three adjacent rows representing
a triangle
mesh.norms - corresponding array of normal vectors
mesh.tverts - corresponding array of texture coordinates
- Class: BasicMaterial
Represents a material from a 3DS file.
This class, instances of which a BasicModel instance is
usually responsible for creating, lists the material
information.
mat.name - name of the mateiral
mat.ambient - ambient color
mat.diffuse - diffuse color
mat.specular - specular color
mat.shininess - shininess exponent
mat.texture - texture object
mat.twosided - whether to paint both sides
- Module: Dice3DS.example.glmodel
Classes for rendering 3DS models in OpenGL.
Defines some classes (based on Dice3DS.example.basicmodel) with some
additional methods to draw the model in OpenGL, or create a display
list to do so.
- Class: GLModel
Subclass of BasicModel that renders the model in OpenGL.
Provides two methods:
render() - issue the OpenGL commands to draw this model
create_dl() - create an OpenGL display list of this model,
and return the handle.
- Class: GLMesh
Subclass of BasicMesh that renders the mesh in OpenGL.
- Class: GLMaterial
Subclass of BasicMaterial that sets OpenGL material properties.
- Module: Dice3DS.example.gltexture
OpenGL texture object abstraction.
Provides a class that is an abstraction of OpenGL texture objects. It
can create textures from image files, and automatically generates
mipmaps if requested.
- Class: Texture
An OpenGL texture object.
This class uses PIL to create and bind an OpenGL texture
object.
Features:
It automatically creates mipmaps if a mipmap rendering
option is selected.
Handles alpha channel in images.
Methods:
tex.enable() - enable OpenGL texturing of proper dimensions
tex.disable() - disable OpenGL texturing of proper dimensions
tex.bind() - bind this texture
tex.real() - whether this is really a texture (which it is)
tex.destroy() - delete OpenGL texture object
- Class: NonTexture
An OpenGL non texture object.
This is just a sort of null class indicating an object has no
texture. It provides the same methods the Texture class does,
but they do nothing.
- Module: Dice3DS.example.config
Configuration parameters for Dice3DS example libraries.
Defines variables that determine what package is used to perform
certain tasks.
OPENGL_PACKAGE
Determines which OpenGL wrapper package to use.
Can be "PyOpenGL" or "pyglet".
IMAGE_LOAD_PACKAGE
Determines which package to use when loading images.
Can be "PIL", "pyglet", or "pygame".
These value must be set before importing any other modules in the
example package; otherwise they have no effect.
- Module: Dice3DS.example.anygl
Import the appropriate OpenGL wrapper.
The package used to wrap OpenGL can be configured by setting
Dice3DS.example.config.OPENGL_PACKAGE. The wrappers supported are
PyOpenGL and pyglet.
All the symbols in the OpenGL wrapper package will be imported into
the module's namespace. In addition, there are a few extra symbols
defined so that code written for PyOpenGL can be made compatible with
pyglet.gl.
- Module: Dice3DS.example.anyimgload
Define a function to load an image with the appropriate package.
The package used to load images can be configured by setting
Dice3DS.example.config.IMAGE_LOAD_PACKAGE. The image loaders
supported are PIL (Python Imaging Library), pygame, and pyglet.
The function defined is called load_image(). It implements a file
search because 3DS files don't care about case or even filename
extensions. It will upsize textures to the next power of 2 if
necessary. It is zipfile aware (helpful since some 3DS models are
delivered as zips, although the stupid messages they cat to the end of
zip files can break this). The output of this function is a class
containing image raw data in a format suitable for OpenGL textures,
format ("RGB" or "RGBA"), width, and height.
- Class: ImageData
Holds data for a given image.
Has four attributes:
raw_data: the raw image data
format: RGBA or RGBX
width: image width in pixels
height: image height in pixels
- Function: load_iamge
Loads an image using the configured
image_data = load_image(filespec)
filespec is one of three options for loading a file:
- a filename
- a tuple (zip_file_object, filename_within_zipfile)
- an open file stream (for reading)
Returns an ImageData() instance.
The image loading library used will depend on the value of the
configuration variable Dice3DS.example.config.IMAGE_LOAD_PACKAGE.
- Module: Dice3DS.example.modelloader
Example of loading 3DS models.
Provides functions to load a 3DS model and creating a GLModel (or
BasicModel) from it. Shows how to load models from the filesystem, or
directly from a zip file.
- Function: load_model_from_filesystem
Load a model from the filesystem.
model = load_model_from_filesystem(filename,
modelclass=glmodel.GLModel,
texture_options=())
This loads a model, where the textures are to be found in the
filesystem. filename is the 3DS file to load. Any textures
listed in it are expected to be found relative to the current
directory.
It creates a model of the given class, and creates textures
with the given texture options.
- Function: load_model_from_zipfile
Load a model from a zipfile.
model = load_model_from_filesystem(zipfilename,
archivename, modelclass=glmodel.GLModel,
texture_options=())
This loads a model, where the 3DS file and the textures are
found inside a zipfile. zipfilename is the name of the
zipfile. arcfilename is the name of the 3DS file inside the
zipfile archive. Any textures listed in it are expected to be
found inside the zipfile as well.
It creates a model of the given class, and creates textures
with the given texture options.
- Module: Dice3DS.example.importing3ds
Example of how to import 3DS files into Blender.
Background: The most recent versions of Blender include 3DS
Import/Export plugins, which work ok. However, like most of the
plugins, they are not so flexible and easy to modify if you need to
change something (say, for example, to implement a custom way to deal
with smoothing). Dice3DS, however, lets you whip up very concise and
straightforward conversion routines. So if you need to wheel and deal
with importing 3DS files, I'd suggest starting from here instead.
The drawback of all this is that you'd have to install Python and
numpy, whereas the offical plugin works in Blender without having to
install either.
This module is *not* a Blender plugin. It's more like a library. You
could import it from a plugin, or straight from the text area.
- Function: import3ds
Import a 3DS file into the current scene of Blender.
Usage:
import3ds(filename)
Implementation notes:
1. Must be run from within Blender, of course.
2. It does not handle smoothing data at all: output is all solid.
3. If a texture cannot be found, it does a case-insensitive search
of the directory; useful on case-sensitive systems when reading
a 3DS file made on a case-insensitive systems by some ignorant
fellow.
4. If a texture is not in PNG or JPEG format, it saves a copy of
the texture as a PNG file in the same directory. Blender uses
the copy for the texture, since Blender only understands JPEG
and PNG. Useful for when said ignorant fellow uses a BMP file.
Examples
Here is an example of creating a 3DS DOM from scratch, and writing it
to output. In the snippet, pointarray is a n-by-3 numpy
array listing vertex coordinates, and facearray is an m-by-3
listing of point indices making up a face.
from Dice3DS import dom3ds
import numpy
def output_model(facearray,pointarray,filename):
n = len(pointarray)
m = len(facearray)
# First, add a fourth column to facearray for the face flags.
# Dice3DS pretty much ignores these flags, but they're required by
# the 3DS format.
padfacearray = numpy.zeros((n,4),numpy.uint32)
padfacearray[:,:3] = facearray
padfacearray[:,3] = 7
# Create a smoothing group array. When the whole model is to be
# smooth, it is appropriate to set the array to all ones.
smoothing = numpy.ones(m,numpy.uint32)
# Create an N_TRI_OBJECT, which is basically a big list of
# triangles. This object lists the vertices, faces, smoothing
# groups, and the transformation matrix, which is set to identity.
#
# Note that you can initialize a 3DS chunk in one of two ways: by
# passing keyword arguments to its constructor, or assigning
# attributes.
obj = dom3ds.N_TRI_OBJECT()
obj.points = dom3ds.POINT_ARRAY(npoints=n,array=pointarray)
obj.faces = dom3ds.FACE_ARRAY(nfaces=m,array=padfacearray)
obj.faces.smoothing = dom3ds.SMOOTH_GROUP(array=smoothing)
obj.matrix = dom3ds.MESH_MATRIX(array=numpy.identity(4,numpy.float32))
# Create a named object. Give it the name OBJECT, and set its
# object to the N_TRI_OBJECT created above.
nobj = dom3ds.NAMED_OBJECT(name="OBJECT",obj=obj)
# Now, create the stuff that appears in every (or almost every)
# 3DS file.
dom = dom3ds.M3DMAGIC()
dom.version = dom3ds.M3D_VERSION(number=3)
dom.mdata = dom3ds.MDATA()
dom.mdata.scale = dom3ds.MASTER_SCALE(value=1.0)
dom.mdata.objects = [ nobj ]
# Output the 3DS file
dom3ds.write_3ds_file(filename,dom)
Scripts
Dice3DS provides is two scripts, dump3ds and view3ds:
- dump3ds dumps the a 3DS file to output in human-readable form.
- view3ds is a simple 3DS model viewer. It requires one of the following combinations of libraries to work:
- PIL and PyOpenGL
- PyGame and PyOpenGL
- pyglet
PIL will be used to load images if it's available, otherwise it will use PyGame's or pyglet's image loaders.
You can get help at the command line by calling them with the
-h or --help option.
Downloads
Dice3DS is licensed under a BSD-style license.
Version 0.13 is the most recent version. It can be downloaded from
the Dice3DS home
page.
Links
Here are links to the homepages of the libraries used by Dice3DS:
Here are some links to other libraries and information about the 3DS
file format:
About the name
I originally was going to call it Utility3DS or Util3DS. (Lib3DS was
taken, and Py3DS is just lame, as all Py* names are). However,
Utility is a boring and aesthetically unpleasing name, and didn't
sound right. (I couldn't call it 3DSUtil because it needed to be a
legal Python symbol.
So, I decided to name it Dice3DS in honor the famous cliche "It
slices, it dices!" Definitely cooler and more euphonic than
Util3DS.
Feedback
Comments, questions?
My email address can be found on the Dice3DS
web page.
(I don't list my email address in this document because I change it
from time to time to avoid spammers.)
Copyright (c) 2001-2010 Carl Banks. All Rights Reserved.
|
|