I have this script working (thanks to CoDEmanX) and have mostly tweaked it the way I want it, except for one detail.
When exporting an object, it uses the .blend name for the name of the exported file. Can anyone see an easy way of making it use the name of the object instead?
bl_info = { "name": "Railroad Tycoon 3 (.3dp)",
"author": "CoDEmanX",
"version": (0, 5),
"blender": (2, 74, 0),
"location": "File > Import | File > Export",
"description": "Export (and partially import) 3dp file format.",
"warning": "",
"wiki_url": "",
"category": "Import-Export",
}
import bpy
import bmesh
import struct
import os
from binascii import unhexlify
from bpy_extras.io_utils import ExportHelper, ImportHelper
from bpy.props import StringProperty
from bpy.types import Operator
def write(context, filepath):
class QUAD:
BEAUTY = 0
FIXED = 1
ALTERNATE = 2
SHORTEDGE = 3
class NGON:
BEAUTY = 0
EARCLIP = 1
try:
file = open(filepath, "wb")
file.write(unhexlify(
"33 44 50 46 04 00 01 00 33 44 4D 44 01 00 00 00"
"00 00 00 00 00 00 00 00 00 00 00 00 49 4E 53 54"
"00 00 00 00 00 00 00 00".replace(" ", ""))
)
verts_offset = file.tell()
scene = context.scene
verts_total = sum(len(ob.data.vertices) for ob in bpy.context.selected_objects if ob.type == 'MESH')
faces_offset = verts_total * 12 + verts_offset
vc = 0
tc = 0
for ob in bpy.context.selected_objects:
if ob.type != 'MESH':
continue
bm = bmesh.new()
bm.from_object(ob, scene)
bm.transform(ob.matrix_world)
bmesh.ops.triangulate(bm, faces=bm.faces, quad_method=QUAD.BEAUTY, ngon_method=NGON.BEAUTY)
uv_layer = bm.loops.layers.uv.active
if uv_layer is None:
raise Exception("'{}' has no active UV map.".format(ob.name))
file.seek(verts_offset)
for v in bm.verts:
file.write(struct.pack("<3f", *v.co))
verts_offset = file.tell()
file.seek(faces_offset)
for f in bm.faces:
for i in range(3):
file.write(struct.pack("<i", f.verts[i].index + vc))
for i in range(3):
file.write(struct.pack("<3f", *f.loops[i].calc_normal()))
for i in range(3):
u, v = f.loops[i][uv_layer].uv
file.write(struct.pack("<2f", u, 1-v))
file.write(b"\0" * 4)
faces_offset = file.tell()
vc += len(bm.verts)
tc += len(bm.faces)
bm.free()
file.seek(32)
file.write(struct.pack("<2i", vc, tc))
except (IOError, OSError) as err:
return "There was trouble writing the file '{}':
{}".format(
bpy.path.basename(filepath), err)
except Exception as err:
return "An error occurred:
{}".format(err)
finally:
file.close()
def read(context, filepath):
try:
scene = context.scene
faces = []
uvs = []
invert_v = lambda x: 1 - x[1] if x[0] % 2 else x[1]
file = open(filepath, 'rb')
file.seek(32)
vert_count, face_count = struct.unpack("<2i", file.read(8))
verts = struct.unpack("<{}f".format(3*vert_count), file.read(12*vert_count))
for i in range(face_count):
faces.extend(struct.unpack("<3i", file.read(12)))
file.seek(36, os.SEEK_CUR)
uvs.extend(map(invert_v, enumerate(struct.unpack("<6f", file.read(24)))))
file.seek(4, os.SEEK_CUR)
name = filepath.split('\\')[-1].split('/')[-1]
me = bpy.data.meshes.new(name)
me.vertices.add(vert_count)
me.vertices.foreach_set("co", verts)
me.loops.add(3*face_count)
me.loops.foreach_set("vertex_index", faces)
me.polygons.add(face_count)
me.polygons.foreach_set("loop_start", range(0, 3*face_count, 3))
me.polygons.foreach_set("loop_total", [3] * face_count)
uv_tex = me.uv_textures.new()
uv_layer = me.uv_layers[-1]
uv_layer.data.foreach_set("uv", uvs)
me.validate()
me.update(True)
me.calc_normals()
for ob in scene.objects:
ob.select = False
ob = bpy.data.objects.new(name, me)
ob.select = True
scene.objects.link(ob)
scene.objects.active = ob
scene.update()
except (IOError, OSError):
return "There was trouble reading the file '{}':
{}".format(
bpy.path.basename(filepath), err)
except Exception as err:
return "An error occurred:
{}".format(err)
finally:
file.close()
class Export3dp(Operator, ExportHelper):
"""Export Railroad Tycoon 3 (.3dp)"""
bl_idname = "export_mesh.3dp"
bl_label = "Export 3dp"
# ExportHelper mixin class uses this
filename_ext = ".3dp"
filter_glob = StringProperty(
default="*.3dp",
options={'HIDDEN'},
)
def execute(self, context):
err = write(context, self.filepath)
if err:
self.report({'ERROR'}, err)
return {'CANCELLED'}
return {'FINISHED'}
class Import3dp(Operator, ImportHelper):
"""Import Railroad Tycoon 3 (.3dp)
NOTE: Does not support vertex normals and UV mapping
"""
bl_idname = "import_mesh.3dp"
bl_label = "Import 3dp"
# ImportHelper mixin class uses this
filename_ext = ".3dp"
filter_glob = StringProperty(
default="*.3dp",
options={'HIDDEN'},
)
def execute(self, context):
err = read(context, self.filepath)
if err is not None:
self.report({'ERROR'}, err)
return {'CANCELLED'}
return {'FINISHED'}
def menu_func_export(self, context):
self.layout.operator(Export3dp.bl_idname, text="Railroad Tycoon 3 (.3dp)")
def menu_func_import(self, context):
self.layout.operator(Import3dp.bl_idname, text="Railroad Tycoon 3 (.3dp)")
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_export.append(menu_func_export)
bpy.types.INFO_MT_file_import.append(menu_func_import)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
bpy.types.INFO_MT_file_import.remove(menu_func_import)
if __name__ == "__main__":
register()