Skip to content
Snippets Groups Projects
export_3ds.py 50 KiB
Newer Older
  • Learn to ignore specific revisions
  •     mesh_chunk = _3ds_chunk(OBJECT_MESH)
    
        # add vertex chunk:
        mesh_chunk.add_subchunk(make_vert_chunk(vert_array))
    
        # add faces chunk:
        mesh_chunk.add_subchunk(make_faces_chunk(tri_list, mesh, materialDict))
    
        # if available, add uv chunk:
        if uv_array:
            mesh_chunk.add_subchunk(make_uv_chunk(uv_array))
    
    
        # mesh_chunk.add_subchunk(make_matrix_4x3_chunk(matrix))
    
    
        # create transformation matrix chunk
        matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
        obj_matrix = matrix.transposed().to_3x3()
    
        if ob.parent is None:
            obj_translate = translation[ob.name]
    
        else:  # Calculate child matrix translation relative to parent
    
            obj_translate = translation[ob.name].cross(-1 * translation[ob.parent.name])
    
    
        matrix_chunk.add_variable("xx", _3ds_float(obj_matrix[0].to_tuple(6)[0]))
        matrix_chunk.add_variable("xy", _3ds_float(obj_matrix[0].to_tuple(6)[1]))
        matrix_chunk.add_variable("xz", _3ds_float(obj_matrix[0].to_tuple(6)[2]))
        matrix_chunk.add_variable("yx", _3ds_float(obj_matrix[1].to_tuple(6)[0]))
        matrix_chunk.add_variable("yy", _3ds_float(obj_matrix[1].to_tuple(6)[1]))
        matrix_chunk.add_variable("yz", _3ds_float(obj_matrix[1].to_tuple(6)[2]))
        matrix_chunk.add_variable("zx", _3ds_float(obj_matrix[2].to_tuple(6)[0]))
        matrix_chunk.add_variable("zy", _3ds_float(obj_matrix[2].to_tuple(6)[1]))
        matrix_chunk.add_variable("zz", _3ds_float(obj_matrix[2].to_tuple(6)[2]))
        matrix_chunk.add_variable("tx", _3ds_float(obj_translate.to_tuple(6)[0]))
        matrix_chunk.add_variable("ty", _3ds_float(obj_translate.to_tuple(6)[1]))
        matrix_chunk.add_variable("tz", _3ds_float(obj_translate.to_tuple(6)[2]))
    
        mesh_chunk.add_subchunk(matrix_chunk)
    
    
        return mesh_chunk
    
    
    ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
    def make_kfdata(start=0, stop=0, curtime=0):
        """Make the basic keyframe data chunk"""
        kfdata = _3ds_chunk(KFDATA)
    
        kfhdr = _3ds_chunk(KFDATA_KFHDR)
        kfhdr.add_variable("revision", _3ds_ushort(0))
        # Not really sure what filename is used for, but it seems it is usually used
        # to identify the program that generated the .3ds:
        kfhdr.add_variable("filename", _3ds_string("Blender"))
        kfhdr.add_variable("animlen", _3ds_uint(stop-start))
    
        kfseg = _3ds_chunk(KFDATA_KFSEG)
        kfseg.add_variable("start", _3ds_uint(start))
        kfseg.add_variable("stop", _3ds_uint(stop))
    
        kfcurtime = _3ds_chunk(KFDATA_KFCURTIME)
        kfcurtime.add_variable("curtime", _3ds_uint(curtime))
    
        kfdata.add_subchunk(kfhdr)
        kfdata.add_subchunk(kfseg)
        kfdata.add_subchunk(kfcurtime)
        return kfdata
    
    def make_track_chunk(ID, obj):
        """Make a chunk for track data.
    
        Depending on the ID, this will construct a position, rotation or scale track."""
        track_chunk = _3ds_chunk(ID)
        track_chunk.add_variable("track_flags", _3ds_ushort())
        track_chunk.add_variable("unknown", _3ds_uint())
        track_chunk.add_variable("unknown", _3ds_uint())
        track_chunk.add_variable("nkeys", _3ds_uint(1))
        # Next section should be repeated for every keyframe, but for now, animation is not actually supported.
        track_chunk.add_variable("tcb_frame", _3ds_uint(0))
        track_chunk.add_variable("tcb_flags", _3ds_ushort())
        if obj.type=='Empty':
            if ID==POS_TRACK_TAG:
                # position vector:
                track_chunk.add_variable("position", _3ds_point_3d(obj.getLocation()))
            elif ID==ROT_TRACK_TAG:
                # rotation (quaternion, angle first, followed by axis):
                q = obj.getEuler().to_quaternion()  # XXX, todo!
                track_chunk.add_variable("rotation", _3ds_point_4d((q.angle, q.axis[0], q.axis[1], q.axis[2])))
            elif ID==SCL_TRACK_TAG:
                # scale vector:
                track_chunk.add_variable("scale", _3ds_point_3d(obj.getSize()))
        else:
            # meshes have their transformations applied before
            # exporting, so write identity transforms here:
            if ID==POS_TRACK_TAG:
                # position vector:
                track_chunk.add_variable("position", _3ds_point_3d((0.0,0.0,0.0)))
            elif ID==ROT_TRACK_TAG:
                # rotation (quaternion, angle first, followed by axis):
                track_chunk.add_variable("rotation", _3ds_point_4d((0.0, 1.0, 0.0, 0.0)))
            elif ID==SCL_TRACK_TAG:
                # scale vector:
                track_chunk.add_variable("scale", _3ds_point_3d((1.0, 1.0, 1.0)))
    
        return track_chunk
    
    def make_kf_obj_node(obj, name_to_id):
        """Make a node chunk for a Blender object.
    
        Takes the Blender object as a parameter. Object id's are taken from the dictionary name_to_id.
        Blender Empty objects are converted to dummy nodes."""
    
        name = obj.name
        # main object node chunk:
        kf_obj_node = _3ds_chunk(KFDATA_OBJECT_NODE_TAG)
        # chunk for the object id:
        obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID)
        # object id is from the name_to_id dictionary:
        obj_id_chunk.add_variable("node_id", _3ds_ushort(name_to_id[name]))
    
        # object node header:
        obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR)
        # object name:
        if obj.type == 'Empty':
            # Empties are called "$$$DUMMY" and use the OBJECT_INSTANCE_NAME chunk
            # for their name (see below):
            obj_node_header_chunk.add_variable("name", _3ds_string("$$$DUMMY"))
        else:
            # Add the name:
            obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
        # Add Flag variables (not sure what they do):
        obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0))
        obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
    
        # Check parent-child relationships:
        parent = obj.parent
        if (parent is None) or (parent.name not in name_to_id):
            # If no parent, or the parents name is not in the name_to_id dictionary,
            # parent id becomes -1:
            obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1))
        else:
            # Get the parent's id from the name_to_id dictionary:
            obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_to_id[parent.name]))
    
        # Add pivot chunk:
        obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT)
        obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(obj.getLocation()))
        kf_obj_node.add_subchunk(obj_pivot_chunk)
    
        # add subchunks for object id and node header:
        kf_obj_node.add_subchunk(obj_id_chunk)
        kf_obj_node.add_subchunk(obj_node_header_chunk)
    
        # Empty objects need to have an extra chunk for the instance name:
        if obj.type == 'Empty':
            obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME)
            obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name)))
            kf_obj_node.add_subchunk(obj_instance_name_chunk)
    
        # Add track chunks for position, rotation and scale:
        kf_obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, obj))
        kf_obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, obj))
        kf_obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, obj))
    
        return kf_obj_node
    '''
    
    
    def save(operator,
             context, filepath="",
             use_selection=True,
             global_matrix=None,
             ):
    
        import time
        from bpy_extras.io_utils import create_derived_objects, free_derived_objects
    
        """Save the Blender scene to a 3ds file."""
    
        # Time the export
    
        duration = time.time()
    
        # Blender.Window.WaitCursor(1)
    
    
        if global_matrix is None:
            global_matrix = mathutils.Matrix()
    
        if bpy.ops.object.mode_set.poll():
            bpy.ops.object.mode_set(mode='OBJECT')
    
    
        scene = context.scene
        layer = context.view_layer
        #depsgraph = context.evaluated_depsgraph_get()
    
    
        # Initialize the main chunk (primary):
        primary = _3ds_chunk(PRIMARY)
        # Add version chunk:
        version_chunk = _3ds_chunk(VERSION)
        version_chunk.add_variable("version", _3ds_uint(3))
        primary.add_subchunk(version_chunk)
    
    
        # Init main object info chunk:
    
        object_info = _3ds_chunk(OBJECTINFO)
    
        mesh_version = _3ds_chunk(MESHVERSION)
        mesh_version.add_variable("mesh", _3ds_uint(3))
        object_info.add_subchunk(mesh_version)
    
        # Add MASTERSCALE element
        mscale = _3ds_chunk(MASTERSCALE)
        mscale.add_variable("scale", _3ds_float(1))
        object_info.add_subchunk(mscale)
    
        # Add AMBIENT color
        if scene.world is not None:
            ambient_chunk = _3ds_chunk(AMBIENTLIGHT)
            ambient_light = _3ds_chunk(RGB)
            ambient_light.add_variable("ambient", _3ds_float_color(scene.world.color))
            ambient_chunk.add_subchunk(ambient_light)
            object_info.add_subchunk(ambient_chunk)
    
    
        ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
        # init main key frame data chunk:
        kfdata = make_kfdata()
        '''
    
        # Make a list of all materials used in the selected meshes (use a dictionary,
        # each material is added once):
        materialDict = {}
        mesh_objects = []
    
        if use_selection:
    
            objects = [ob for ob in scene.objects if not ob.hide_viewport and ob.select_get(view_layer=layer)]
    
            objects = [ob for ob in scene.objects if not ob.hide_viewport]
    
        light_objects = [ob for ob in objects if ob.type == 'LIGHT']
        camera_objects = [ob for ob in objects if ob.type == 'CAMERA']
    
    
        for ob in objects:
            # get derived objects
            free, derived = create_derived_objects(scene, ob)
    
            if derived is None:
                continue
    
    
            for ob_derived, mtx in derived:
    
                if ob.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
                    continue
    
    
                #ob_derived_eval = ob_derived.evaluated_get(depsgraph)
    
                    data = ob_derived.to_mesh()
    
                except:
                    data = None
    
                if data:
    
                    matrix = global_matrix @ mtx
    
                    data.transform(matrix)
                    mesh_objects.append((ob_derived, data, matrix))
    
                    ma_ls = data.materials
                    ma_ls_len = len(ma_ls)
    
    
                    # get material/image tuples.
    
                    if data.uv_layers:
                        if not ma_ls:
                            ma = ma_name = None
    
                        for f, uf in zip(data.polygons, data.uv_layers.active.data):
                            if ma_ls:
                                ma_index = f.material_index
                                if ma_index >= ma_ls_len:
                                    ma_index = f.material_index = 0
                                ma = ma_ls[ma_index]
                                ma_name = None if ma is None else ma.name
    
                            # else there already set to none
    
    
                            img = get_uv_image(ma)
    
                            img_name = None if img is None else img.name
    
    
                            materialDict.setdefault((ma_name, img_name), (ma, img))
    
                        for ma in ma_ls:
                            if ma:  # material may be None so check its not.
                                materialDict.setdefault((ma.name, None), (ma, None))
    
                        for f in data.polygons:
                            if f.material_index >= ma_ls_len:
    
                                f.material_index = 0
    
    
                    # ob_derived_eval.to_mesh_clear()
    
    
            if free:
                free_derived_objects(ob)
    
        # Make material chunks for all materials used in the meshes:
    
        for ma_image in materialDict.values():
            object_info.add_subchunk(make_material_chunk(ma_image[0], ma_image[1]))
    
    
        # Give all objects a unique ID and build a dictionary from object name to object id:
    
        translation = {}  # collect translation for transformation matrix
        #name_to_id = {}
        for ob, data, matrix in mesh_objects:
            translation[ob.name] = ob.location
            #name_to_id[ob.name]= len(name_to_id)
    
        """
        #for ob in empty_objects:
        #    name_to_id[ob.name]= len(name_to_id)
        """
    
        # Create object chunks for all meshes:
        i = 0
    
        for ob, mesh, matrix in mesh_objects:
    
            # create a new object chunk
            object_chunk = _3ds_chunk(OBJECT)
    
            # set the object name
            object_chunk.add_variable("name", _3ds_string(sane_name(ob.name)))
    
            # make a mesh chunk out of the mesh:
    
            object_chunk.add_subchunk(make_mesh_chunk(ob, mesh, matrix, materialDict, translation))
    
    
            # ensure the mesh has no over sized arrays
            # skip ones that do!, otherwise we cant write since the array size wont
            # fit into USHORT.
            if object_chunk.validate():
                object_info.add_subchunk(object_chunk)
            else:
                operator.report({'WARNING'}, "Object %r can't be written into a 3DS file")
    
            ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
            # make a kf object node for the object:
            kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
            '''
    
    
            # if not blender_mesh.users:
            # bpy.data.meshes.remove(blender_mesh)
    
            #blender_mesh.vertices = None
    
            i += i
    
        # Create chunks for all empties:
        ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
        for ob in empty_objects:
            # Empties only require a kf object node:
            kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
            pass
        '''
    
    
        # Create light object chunks
        for ob in light_objects:
            object_chunk = _3ds_chunk(OBJECT)
            light_chunk = _3ds_chunk(OBJECT_LIGHT)
            color_float_chunk = _3ds_chunk(RGB)
            energy_factor = _3ds_chunk(LIGHT_MULTIPLIER)
            object_chunk.add_variable("light", _3ds_string(sane_name(ob.name)))
            light_chunk.add_variable("location", _3ds_point_3d(ob.location))
            color_float_chunk.add_variable("color", _3ds_float_color(ob.data.color))
    
            energy_factor.add_variable("energy", _3ds_float(ob.data.energy * .001))
    
            light_chunk.add_subchunk(color_float_chunk)
            light_chunk.add_subchunk(energy_factor)
    
            if ob.data.type == 'SPOT':
                cone_angle = math.degrees(ob.data.spot_size)
    
                hotspot = cone_angle - (ob.data.spot_blend * math.floor(cone_angle))
                hypo = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1])
                pos_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2]))
                pos_y = ob.location[1] + (ob.location[0] * math.tan(math.radians(90) - ob.rotation_euler[2]))
                pos_z = hypo * math.tan(math.radians(90) - ob.rotation_euler[0])
    
                spotlight_chunk = _3ds_chunk(LIGHT_SPOTLIGHT)
                spot_roll_chunk = _3ds_chunk(LIGHT_SPOTROLL)
                spotlight_chunk.add_variable("target", _3ds_point_3d((pos_x, pos_y, pos_z)))
    
                spotlight_chunk.add_variable("hotspot", _3ds_float(round(hotspot, 4)))
                spotlight_chunk.add_variable("angle", _3ds_float(round(cone_angle, 4)))
                spot_roll_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler[1], 6)))
    
                spotlight_chunk.add_subchunk(spot_roll_chunk)
                light_chunk.add_subchunk(spotlight_chunk)
    
            # Add light to object info
            object_chunk.add_subchunk(light_chunk)
            object_info.add_subchunk(object_chunk)
    
        # Create camera object chunks
        for ob in camera_objects:
            object_chunk = _3ds_chunk(OBJECT)
            camera_chunk = _3ds_chunk(OBJECT_CAMERA)
    
            diagonal = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1])
            focus_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2]))
            focus_y = ob.location[1] + (ob.location[0] * math.tan(math.radians(90) - ob.rotation_euler[2]))
            focus_z = diagonal * math.tan(math.radians(90) - ob.rotation_euler[0])
    
            object_chunk.add_variable("camera", _3ds_string(sane_name(ob.name)))
            camera_chunk.add_variable("location", _3ds_point_3d(ob.location))
            camera_chunk.add_variable("target", _3ds_point_3d((focus_x, focus_y, focus_z)))
    
            camera_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler[1], 6)))
    
            camera_chunk.add_variable("lens", _3ds_float(ob.data.lens))
            object_chunk.add_subchunk(camera_chunk)
            object_info.add_subchunk(object_chunk)
    
    
        # Add main object info chunk to primary chunk:
        primary.add_subchunk(object_info)
    
        ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
        # Add main keyframe data chunk to primary chunk:
        primary.add_subchunk(kfdata)
        '''
    
        # At this point, the chunk hierarchy is completely built.
    
        # Check the size:
        primary.get_size()
        # Open the file for writing:
        file = open(filepath, 'wb')
    
        # Recursively write the chunks to file:
        primary.write(file)
    
        # Close the file:
        file.close()
    
        # Clear name mapping vars, could make locals too
        del name_unique[:]
        name_mapping.clear()
    
        # Debugging only: report the exporting time:
    
        # Blender.Window.WaitCursor(0)
    
        print("3ds export time: %.2f" % (time.time() - duration))
    
    
        # Debugging only: dump the chunk hierarchy:
    
        # primary.dump()
    
    
        return {'FINISHED'}