ソースを参照

Add .mesh exporter for Blender 2.58+ based on the official OBJ exporter

Daniele Bartolini 12 年 前
コミット
9e36aeb9d4
2 ファイル変更560 行追加0 行削除
  1. 163 0
      exporters/blender-2.68/__init__.py
  2. 397 0
      exporters/blender-2.68/export_crown.py

+ 163 - 0
exporters/blender-2.68/__init__.py

@@ -0,0 +1,163 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8-80 compliant>
+
+bl_info = {
+    "name": "Crown Engine .mesh format",
+    "author": "Crown Engine Team",
+    "blender": (2, 58, 0),
+    "location": "File > Import-Export",
+    "description": "Export Crown Engine .mesh models",
+    "warning": "",
+    "tracker_url": "",
+    "support": 'OFFICIAL',
+    "category": "Import-Export"}
+
+if "bpy" in locals():
+    import imp
+    if "export_crown" in locals():
+        imp.reload(export_crown)
+
+
+import bpy
+from bpy.props import (BoolProperty,
+                       FloatProperty,
+                       StringProperty,
+                       EnumProperty,
+                       )
+from bpy_extras.io_utils import (ExportHelper,
+                                 path_reference_mode,
+                                 axis_conversion,
+                                 )
+
+class ExportCrownMesh(bpy.types.Operator, ExportHelper):
+    """Save a Crown Engine .mesh model"""
+
+    bl_idname = "model.mesh"
+    bl_label = 'Export .mesh'
+    bl_options = {'PRESET'}
+
+    filename_ext = ".mesh"
+    filter_glob = StringProperty(
+            default="*.mesh;",
+            options={'HIDDEN'},
+            )
+
+    # context group
+    use_selection = BoolProperty(
+            name="Selection Only",
+            description="Export selected objects only",
+            default=False,
+            )
+
+    # object group
+    use_mesh_modifiers = BoolProperty(
+            name="Apply Modifiers",
+            description="Apply modifiers (preview resolution)",
+            default=True,
+            )
+
+    # extra data group
+    use_normals = BoolProperty(
+            name="Include Normals",
+            description="",
+            default=True,
+            )
+    use_uvs = BoolProperty(
+            name="Include UVs",
+            description="Write out the active UV coordinates",
+            default=True,
+            )
+    use_triangles = BoolProperty(
+            name="Triangulate Faces",
+            description="Convert all faces to triangles",
+            default=True,
+            )
+
+    # grouping group
+    keep_vertex_order = BoolProperty(
+            name="Keep Vertex Order",
+            description="",
+            default=False,
+            )
+
+    axis_forward = EnumProperty(
+            name="Forward",
+            items=(('X', "X Forward", ""),
+                   ('Y', "Y Forward", ""),
+                   ('Z', "Z Forward", ""),
+                   ('-X', "-X Forward", ""),
+                   ('-Y', "-Y Forward", ""),
+                   ('-Z', "-Z Forward", ""),
+                   ),
+            default='-Z',
+            )
+    axis_up = EnumProperty(
+            name="Up",
+            items=(('X', "X Up", ""),
+                   ('Y', "Y Up", ""),
+                   ('Z', "Z Up", ""),
+                   ('-X', "-X Up", ""),
+                   ('-Y', "-Y Up", ""),
+                   ('-Z', "-Z Up", ""),
+                   ),
+            default='Y',
+            )
+    global_scale = FloatProperty(
+            name="Scale",
+            min=0.01, max=1000.0,
+            default=1.0,
+            )
+
+    check_extension = True
+
+    def execute(self, context):
+        from . import export_crown
+
+        from mathutils import Matrix
+        keywords = self.as_keywords(ignore=("axis_forward",
+                                            "axis_up",
+                                            "global_scale",
+                                            "check_existing",
+                                            "filter_glob",
+                                            ))
+
+        global_matrix = (Matrix.Scale(self.global_scale, 4) *
+                         axis_conversion(to_forward=self.axis_forward,
+                                         to_up=self.axis_up,
+                                         ).to_4x4())
+
+        keywords["global_matrix"] = global_matrix
+        return export_crown.save(self, context, **keywords)
+
+def menu_func_export(self, context):
+    self.layout.operator(ExportCrownMesh.bl_idname, text="Crown Engine (.mesh)")
+
+
+def register():
+    bpy.utils.register_module(__name__)
+    bpy.types.INFO_MT_file_export.append(menu_func_export)
+
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+    bpy.types.INFO_MT_file_export.remove(menu_func_export)
+
+if __name__ == "__main__":
+    register()

+ 397 - 0
exporters/blender-2.68/export_crown.py

@@ -0,0 +1,397 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import os
+import time
+
+import bpy
+import mathutils
+import bpy_extras.io_utils
+
+
+def name_compat(name):
+	if name is None:
+		return 'None'
+	else:
+		return name.replace(' ', '_')
+
+
+def mesh_triangulate(me):
+	import bmesh
+	bm = bmesh.new()
+	bm.from_mesh(me)
+	bmesh.ops.triangulate(bm, faces=bm.faces)
+	bm.to_mesh(me)
+	bm.free()
+
+def write_file(filepath, objects, scene,
+			   EXPORT_TRI=True,
+			   EXPORT_NORMALS=True,
+			   EXPORT_UV=True,
+			   EXPORT_APPLY_MODIFIERS=True,
+			   EXPORT_KEEP_VERT_ORDER=False,
+			   EXPORT_GLOBAL_MATRIX=None,
+			   ):
+	"""
+	Basic write function. The context and options must be already set
+	This can be accessed externaly
+	eg.
+	write( '/home/user/project/foobar.mesh', Blender.Object.GetSelected() ) # Using default options.
+	"""
+
+	if EXPORT_GLOBAL_MATRIX is None:
+		EXPORT_GLOBAL_MATRIX = mathutils.Matrix()
+
+	def veckey3d(v):
+		return round(v.x, 6), round(v.y, 6), round(v.z, 6)
+
+	def veckey2d(v):
+		return round(v[0], 6), round(v[1], 6)
+
+	print('Crown Engine .mesh export path: %r' % filepath)
+
+	time1 = time.time()
+
+	file = open(filepath, "wb")
+	def fw(string):
+		file.write(bytes(string, 'UTF-8'))
+
+	# Write Header
+	# FIXME FIXME FIXME
+
+	# Initialize totals, these are updated each object
+	totverts = totuvco = totno = 0
+
+	face_vert_index = 1
+
+	globalNormals = {}
+
+	copy_set = set()
+
+	# Get all meshes
+	for ob_main in objects:
+
+		# ignore dupli children
+		if ob_main.parent and ob_main.parent.dupli_type in {'VERTS', 'FACES'}:
+			# XXX
+			print(ob_main.name, 'is a dupli child - ignoring')
+			continue
+
+		obs = []
+		if ob_main.dupli_type != 'NONE':
+			# XXX
+			print('creating dupli_list on', ob_main.name)
+			ob_main.dupli_list_create(scene)
+
+			obs = [(dob.object, dob.matrix) for dob in ob_main.dupli_list]
+
+			# XXX debug print
+			print(ob_main.name, 'has', len(obs), 'dupli children')
+		else:
+			obs = [(ob_main, ob_main.matrix_world)]
+
+		for ob, ob_mat in obs:
+
+			try:
+				me = ob.to_mesh(scene, EXPORT_APPLY_MODIFIERS, 'PREVIEW', calc_tessface=False)
+			except RuntimeError:
+				me = None
+
+			if me is None:
+				continue
+
+			me.transform(EXPORT_GLOBAL_MATRIX * ob_mat)
+
+			if EXPORT_TRI:
+				# _must_ do this first since it re-allocs arrays
+				mesh_triangulate(me)
+
+			if EXPORT_UV:
+				faceuv = len(me.uv_textures) > 0
+				if faceuv:
+					uv_texture = me.uv_textures.active.data[:]
+					uv_layer = me.uv_layers.active.data[:]
+			else:
+				faceuv = False
+
+			me_verts = me.vertices[:]
+
+			# Make our own list so it can be sorted to reduce context switching
+			face_index_pairs = [(face, index) for index, face in enumerate(me.polygons)]
+			# faces = [ f for f in me.tessfaces ]
+
+			# Edges not supported by Crown Engine .mesh
+			edges = []
+
+			if not (len(face_index_pairs) + len(edges) + len(me.vertices)):  # Make sure there is somthing to write
+
+				# clean up
+				bpy.data.meshes.remove(me)
+
+				continue  # dont bother with this mesh.
+
+			if EXPORT_NORMALS and face_index_pairs:
+				me.calc_normals()
+
+			materials = me.materials[:]
+			material_names = [m.name if m else None for m in materials]
+
+			# avoid bad index errors
+			if not materials:
+				materials = [None]
+				material_names = [name_compat(None)]
+
+			# Sort by Material, then images
+			# so we dont over context switch in the obj file.
+			if EXPORT_KEEP_VERT_ORDER:
+				pass
+			else:
+				if faceuv:
+					sort_func = lambda a: (a[0].material_index,
+											   hash(uv_texture[a[1]].image),
+											   a[0].use_smooth)
+				elif len(materials) > 1:
+					sort_func = lambda a: (a[0].material_index,
+											   a[0].use_smooth)
+				else:
+					# no materials
+					sort_func = lambda a: a[0].use_smooth
+
+				face_index_pairs.sort(key=sort_func)
+
+				del sort_func
+
+			# Set the default mat to no material and no image.
+			contextMat = 0, 0  # Can never be this, so we will label a new material the first chance we get.
+			contextSmooth = None  # Will either be true or false,  set bad to force initialization switch.
+
+			fw("{\n")
+			# Positions
+			fw("    \"position\" : [")
+			for v in me_verts:
+				fw('\n        %.6f, %.6f, %.6f,' % v.co[:])
+			file.seek(-1, 1)
+			fw("]")
+
+			# NORMAL, Smooth/Non smoothed.
+			if EXPORT_NORMALS:
+				fw(",\n    \"normal\" : [")
+				for f, f_index in face_index_pairs:
+					if f.use_smooth:
+						for v_idx in f.vertices:
+							v = me_verts[v_idx]
+							noKey = veckey3d(v.normal)
+							if noKey not in globalNormals:
+								globalNormals[noKey] = totno
+								totno += 1
+								fw('\n        %.6f, %.6f, %.6f,' % noKey)
+					else:
+						# Hard, 1 normal from the face.
+						noKey = veckey3d(f.normal)
+						if noKey not in globalNormals:
+							globalNormals[noKey] = totno
+							totno += 1
+							fw('\n        %.6f, %.6f, %.6f,' % noKey)
+				file.seek(-1, 1)
+				fw("]")
+
+			# UV
+			if faceuv:
+				# in case removing some of these dont get defined.
+				uv = uvkey = uv_dict = f_index = uv_index = uv_ls = uv_k = None
+
+				uv_face_mapping = [None] * len(face_index_pairs)
+
+				uv_dict = {}  # could use a set() here
+
+				fw(",\n    \"texcoord\" : [")
+				for f, f_index in face_index_pairs:
+					uv_ls = uv_face_mapping[f_index] = []
+					for uv_index, l_index in enumerate(f.loop_indices):
+						uv = uv_layer[l_index].uv
+
+						uvkey = veckey2d(uv)
+						try:
+							uv_k = uv_dict[uvkey]
+						except:
+							uv_k = uv_dict[uvkey] = len(uv_dict)
+							fw('\n        %.6f, %.6f,' % uv[:])
+						uv_ls.append(uv_k)
+				file.seek(-1, 1)
+				fw("]")
+
+				uv_unique_count = len(uv_dict)
+
+				del uv, uvkey, uv_dict, f_index, uv_index, uv_ls, uv_k
+				# Only need uv_unique_count and uv_face_mapping
+
+			# Index array
+			fw(',\n    \"index\" : [')
+
+			# Position indices
+			fw('\n        [ ')
+			for f, f_index in face_index_pairs:
+				f_smooth = f.use_smooth
+				f_v = [(vi, me_verts[v_idx]) for vi, v_idx in enumerate(f.vertices)]
+
+				for vi, v in f_v:
+					fw('%d,' % (v.index + totverts))
+			file.seek(-1, 1)
+			fw(' ]')
+
+			# Normal indices
+			if EXPORT_NORMALS:
+				fw(',\n        [ ')
+				for f, f_index in face_index_pairs:
+					f_smooth = f.use_smooth
+					f_v = [(vi, me_verts[v_idx]) for vi, v_idx in enumerate(f.vertices)]
+
+					if f_smooth: # Use smoothed normals
+						for vi, v in f_v:
+							fw('%d, ' % (globalNormals[veckey3d(v.normal)]))
+					else: # No smoothing, face normals
+						no = globalNormals[veckey3d(f.normal)]
+						for vi, v in f_v:
+							fw('%d,' % no)
+				file.seek(-1, 1)
+				fw(' ]')
+
+			# Texcoords
+			if faceuv:
+				fw(',\n        [ ')
+				for f, f_index in face_index_pairs:
+					f_smooth = f.use_smooth
+					f_v = [(vi, me_verts[v_idx]) for vi, v_idx in enumerate(f.vertices)]
+
+					for vi, v in f_v:
+						fw('%d,' % (totuvco + uv_face_mapping[f_index][vi]))
+				file.seek(-1, 1)
+				fw(' ]')
+			fw(']\n')
+
+			# Make the indices global rather then per mesh
+			totverts += len(me_verts)
+			if faceuv:
+				totuvco += uv_unique_count
+
+			# clean up
+			bpy.data.meshes.remove(me)
+
+		if ob_main.dupli_type != 'NONE':
+			ob_main.dupli_list_clear()
+
+	fw('}\n')		
+	file.close()
+
+	# copy all collected files.
+	bpy_extras.io_utils.path_reference_copy(copy_set)
+
+	print("OBJ Export time: %.2f" % (time.time() - time1))
+
+
+def _write(context, filepath,
+			  EXPORT_TRI,  # ok
+			  EXPORT_NORMALS,  # not yet
+			  EXPORT_UV,  # ok
+			  EXPORT_APPLY_MODIFIERS,  # ok
+			  EXPORT_KEEP_VERT_ORDER,
+			  EXPORT_SEL_ONLY,  # ok
+			  EXPORT_ANIMATION,
+			  EXPORT_GLOBAL_MATRIX,
+			  ):  # Not used
+
+	base_name, ext = os.path.splitext(filepath)
+	context_name = [base_name, '', '', ext]  # Base name, scene name, frame number, extension
+
+	scene = context.scene
+
+	# Exit edit mode before exporting, so current object states are exported properly.
+	if bpy.ops.object.mode_set.poll():
+		bpy.ops.object.mode_set(mode='OBJECT')
+
+	orig_frame = scene.frame_current
+
+	# Export an animation?
+	if EXPORT_ANIMATION:
+		scene_frames = range(scene.frame_start, scene.frame_end + 1)  # Up to and including the end frame.
+	else:
+		scene_frames = [orig_frame]  # Dont export an animation.
+
+	# Loop through all frames in the scene and export.
+	for frame in scene_frames:
+		if EXPORT_ANIMATION:  # Add frame to the filepath.
+			context_name[2] = '_%.6d' % frame
+
+		scene.frame_set(frame, 0.0)
+		if EXPORT_SEL_ONLY:
+			objects = context.selected_objects
+		else:
+			objects = scene.objects
+
+		full_path = ''.join(context_name)
+
+		# erm... bit of a problem here, this can overwrite files when exporting frames. not too bad.
+		# EXPORT THE FILE.
+		write_file(full_path, objects, scene,
+				   EXPORT_TRI,
+				   EXPORT_NORMALS,
+				   EXPORT_UV,
+				   EXPORT_APPLY_MODIFIERS,
+				   EXPORT_KEEP_VERT_ORDER,
+				   EXPORT_GLOBAL_MATRIX,
+				   )
+
+	scene.frame_set(orig_frame, 0.0)
+
+	# Restore old active scene.
+#   orig_scene.makeCurrent()
+#   Window.WaitCursor(0)
+
+
+"""
+Currently the exporter lacks these features:
+* multiple scene export (only active scene is written)
+* particles
+"""
+
+
+def save(operator, context, filepath="",
+		 use_triangles=False,
+		 use_normals=False,
+		 use_uvs=True,
+		 use_mesh_modifiers=True,
+		 keep_vertex_order=False,
+		 use_selection=True,
+		 use_animation=False,
+		 global_matrix=None,
+		 ):
+
+	_write(context, filepath,
+		   EXPORT_TRI=use_triangles,
+		   EXPORT_NORMALS=use_normals,
+		   EXPORT_UV=use_uvs,
+		   EXPORT_APPLY_MODIFIERS=use_mesh_modifiers,
+		   EXPORT_KEEP_VERT_ORDER=keep_vertex_order,
+		   EXPORT_SEL_ONLY=use_selection,
+		   EXPORT_ANIMATION=use_animation,
+		   EXPORT_GLOBAL_MATRIX=global_matrix,
+		   )
+
+	return {'FINISHED'}