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:
    1. PIL and PyOpenGL
    2. PyGame and PyOpenGL
    3. 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.