Quake/Doom3 Map exporter: add some basic 'guided splitting' of concave meshes, for...
authorBastien Montagne <montagne29@wanadoo.fr>
Sun, 19 Jul 2015 16:20:46 +0000 (18:20 +0200)
committerBastien Montagne <montagne29@wanadoo.fr>
Sun, 19 Jul 2015 16:20:46 +0000 (18:20 +0200)
To summarize, you have to mark as seam the edges where you want to split your mesh.
Script will then take care of actuall splitting and adding 'fill-in' planes.

Code from motorstep, with quick review & cleanup from myself.

Note that we most likely can do much much better here, but no time for this currently.
Code works, will do for now. :)

io_scene_map/__init__.py
io_scene_map/export_map.py

index b0019ce..f0616a1 100644 (file)
@@ -20,8 +20,8 @@
 
 bl_info = {
     "name": "Quake/Doom3 MAP format",
-    "author": "Campbell Barton, scorpion81, Bastien Montagne",
-    "version": (2, 0, 0),
+    "author": "Campbell Barton, scorpion81, Bastien Montagne, motorstep",
+    "version": (2, 1, 0),
     "blender": (2, 6, 9),
     "location": "File > Export",
     "description": "Export MAP brushes, nurbs surfaces, "
@@ -69,13 +69,14 @@ class ExportMAP(bpy.types.Operator, ExportHelper):
             name="Scale",
             description="Scale everything by this value",
             min=0.01, max=1000.0,
-            default=100.0,
+            default=1.0,
             )
     grid_snap = BoolProperty(
             name="Grid Snap",
             description="Round to whole numbers",
             default=False,
             )
+
     texture_null = StringProperty(
             name="Tex Null",
             description="Texture used when none is assigned",
index 5c868d8..d8e69e5 100644 (file)
@@ -25,6 +25,10 @@ import os
 import mathutils
 from mathutils import Vector
 
+from contextlib import redirect_stdout
+import io
+stdout = io.StringIO()
+
 # TODO, make options
 PREF_SCALE = 1
 PREF_FACE_THICK = 0.1
@@ -380,6 +384,70 @@ def write_node_map(fw, ob):
     return True
 
 
+def split_objects(context, objects):
+    scene = context.scene
+    final_objects = []
+
+    bpy.ops.object.select_all(action='DESELECT')
+    for ob in objects:
+        ob.select = True
+
+    bpy.ops.object.duplicate()
+    objects = bpy.context.selected_objects
+
+    bpy.ops.object.select_all(action='DESELECT')
+
+    tot_ob = len(objects)
+    for i, ob in enumerate(objects):
+        print("Splitting object: %d/%d" % (i, tot_ob))
+        ob.select = True
+        
+        if ob.type == "MESH":
+            scene.objects.active = ob
+            bpy.ops.object.mode_set(mode='EDIT')
+            bpy.ops.mesh.select_all(action='DESELECT')
+            bpy.ops.mesh.select_mode(type='EDGE')
+            bpy.ops.object.mode_set(mode='OBJECT')
+            for edge in ob.data.edges:
+                if edge.use_seam:
+                    edge.select = True
+            bpy.ops.object.mode_set(mode='EDIT')
+            bpy.ops.mesh.edge_split()
+            bpy.ops.mesh.separate(type='LOOSE')
+            bpy.ops.object.mode_set(mode='OBJECT')
+
+            split_objects = context.selected_objects
+            for split_ob in split_objects:
+                assert(split_ob.type == "MESH")
+
+                scene.objects.active = split_ob
+                bpy.ops.object.mode_set(mode='EDIT')
+                bpy.ops.mesh.select_mode(type='EDGE')
+                bpy.ops.mesh.select_all(action="SELECT")
+                bpy.ops.mesh.region_to_loop()
+                bpy.ops.mesh.fill_holes(sides=8)
+                slot_idx = 0
+                for slot_idx, m in enumerate(split_ob.material_slots):                       
+                   if m.name == "textures/common/caulk":
+                      break
+                   #if m.name != "textures/common/caulk":
+                   #   mat = bpy.data.materials.new("textures/common/caulk")
+                   #   bpy.context.object.data.materials.append(mat)
+                split_ob.active_material_index = slot_idx  # we need to use either actual material name or custom property instead of index
+                bpy.ops.object.material_slot_assign()
+                with redirect_stdout(stdout):
+                   bpy.ops.mesh.remove_doubles()
+                bpy.ops.mesh.quads_convert_to_tris()
+                bpy.ops.mesh.tris_convert_to_quads()
+                bpy.ops.object.mode_set(mode='OBJECT')
+            final_objects += split_objects
+
+        ob.select = False
+
+    print(final_objects)
+    return final_objects
+
+
 def export_map(context, filepath):
     """
     pup_block = [\
@@ -431,6 +499,8 @@ def export_map(context, filepath):
         elif type == 'EMPTY':
             obs_empty.append(ob)
 
+    obs_mesh = split_objects(context, obs_mesh)
+
     with open(filepath, 'w') as fl:
         fw = fl.write
 
@@ -444,7 +514,11 @@ def export_map(context, filepath):
             fw('"classname" "worldspawn"\n')
 
         print("\twriting cubes from meshes")
-        for ob in obs_mesh:
+
+        tot_ob = len(obs_mesh)
+        for i, ob in enumerate(obs_mesh):
+            print("Exporting object: %d/%d" % (i, tot_ob))
+
             dummy_mesh = ob.to_mesh(scene, True, 'PREVIEW')
 
             #print len(mesh_split2connected(dummy_mesh))
@@ -581,6 +655,10 @@ def export_map(context, filepath):
             else:
                 print("\t\tignoring %s" % ob.name)
 
+    for ob in obs_mesh:
+        scene.objects.unlink(ob)
+        bpy.data.objects.remove(ob)
+
     print("Exported Map in %.4fsec" % (time.time() - t))
     print("Brushes: %d  Nodes: %d  Lamps %d\n" % (TOTBRUSH, TOTNODE, TOTLAMP))
 
@@ -588,7 +666,7 @@ def export_map(context, filepath):
 def save(operator,
          context,
          filepath=None,
-         global_scale=100.0,
+         global_scale=1.0,
          face_thickness=0.1,
          texture_null="NULL",
          texture_opts='0 0 0 1 1 0 0 0',