diff --git a/addons/io_scene_swbf_msh/msh_mesh_to_blend.py b/addons/io_scene_swbf_msh/msh_mesh_to_blend.py new file mode 100644 index 0000000..61b5921 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_mesh_to_blend.py @@ -0,0 +1,194 @@ +""" Converts msh meshes to Blender counterparts """ + + +import bpy +import bmesh +import math + +from enum import Enum +from typing import List, Set, Dict, Tuple + +from .msh_scene import Scene +from .msh_material_to_blend import * +from .msh_model import * +from .msh_skeleton_utilities import * +from .msh_model_gather import get_is_model_hidden + + +from .crc import * + +import os + + +def validate_segment_geometry(segment : GeometrySegment): + if not segment.positions: + return False + if not segment.triangles and not segment.triangle_strips and not segment.polygons: + return False + if not segment.material_name: + return False + if not segment.normals: + return False + return True + + +def model_to_mesh_object(model: Model, scene : Scene, materials_map : Dict[str, bpy.types.Material]) -> bpy.types.Object: + + blender_mesh = bpy.data.meshes.new(model.name) + + # Per vertex data which will eventually be remapped to loops + vertex_positions = [] + vertex_uvs = [] + vertex_normals = [] + + + vertex_weights_offsets = {} + + + + # Since polygons in a msh segment index into the segment's verts, + # we must keep an offset to index them into the verts of the whole mesh + polygon_index_offset = 0 + + # List of tuples of face indices + polygons = [] + + # Each polygon has an index into the mesh's material list + current_material_index = 0 + polygon_material_indices = [] + + + for segment in model.geometry: + + if not validate_segment_geometry(segment): + continue + + blender_mesh.materials.append(materials_map[segment.material_name]) + + vertex_positions += [tuple(convert_vector_space(p)) for p in segment.positions] + + if segment.texcoords: + vertex_uvs += [tuple(texcoord) for texcoord in segment.texcoords] + else: + vertex_uvs += [(0.0,0.0) for _ in range(len(segment.positions))] + + if segment.normals: + vertex_normals += [tuple(convert_vector_space(n)) for n in segment.normals] + + if segment.weights: + vertex_weights_offsets[polygon_index_offset] = segment.weights + + + segment_polygons = [] + + if segment.triangles: + segment_polygons = [tuple([ind + polygon_index_offset for ind in tri]) for tri in segment.triangles] + elif segment.triangle_strips: + for strip in segment.triangle_strips: + for i in range(len(strip) - 2): + strip_tri = tuple([polygon_index_offset + strip[j] for j in range(i,i+3)]) + segment_polygons.append(strip_tri) + elif segment.polygons: + segment_polygons = [tuple([ind + polygon_index_offset for ind in polygon]) for polygon in segment.polygons] + + polygon_index_offset += len(segment.positions) + + polygons += segment_polygons + + polygon_material_indices += [current_material_index for _ in segment_polygons] + current_material_index += 1 + + + + ''' + Start building the blender mesh + ''' + + # VERTICES + + # This is all we have to do for vertices, other attributes are done per-loop + blender_mesh.vertices.add(len(vertex_positions)) + blender_mesh.vertices.foreach_set("co", [component for vertex_position in vertex_positions for component in vertex_position]) + + + # LOOPS + + flat_indices = [index for polygon in polygons for index in polygon] + + blender_mesh.loops.add(len(flat_indices)) + + # Position indices + blender_mesh.loops.foreach_set("vertex_index", flat_indices) + + # Normals + blender_mesh.create_normals_split() + blender_mesh.loops.foreach_set("normal", [component for i in flat_indices for component in vertex_normals[i]]) + + # UVs + blender_mesh.uv_layers.new(do_init=False) + blender_mesh.uv_layers[0].data.foreach_set("uv", [component for i in flat_indices for component in vertex_uvs[i]]) + + + + # POLYGONS/FACES + + blender_mesh.polygons.add(len(polygons)) + + # Indices of starting loop for each polygon + polygon_loop_start_indices = [] + current_polygon_start_index = 0 + + # Number of loops in this polygon. Polygon i will use + # loops from polygon_loop_start_indices[i] to + # polygon_loop_start_indices[i] + polygon_loop_totals[i] + polygon_loop_totals = [] + + for polygon in polygons: + polygon_loop_start_indices.append(current_polygon_start_index) + + current_polygon_length = len(polygon) + current_polygon_start_index += current_polygon_length + + polygon_loop_totals.append(current_polygon_length) + + blender_mesh.polygons.foreach_set("loop_start", polygon_loop_start_indices) + blender_mesh.polygons.foreach_set("loop_total", polygon_loop_totals) + blender_mesh.polygons.foreach_set("material_index", polygon_material_indices) + blender_mesh.polygons.foreach_set("use_smooth", [True for _ in polygons]) + + blender_mesh.validate(clean_customdata=False) + blender_mesh.update() + + + # Reset custom normals after calling update/validate + reset_normals = [0.0] * (len(blender_mesh.loops) * 3) + blender_mesh.loops.foreach_get("normal", reset_normals) + blender_mesh.normals_split_custom_set(tuple(zip(*(iter(reset_normals),) * 3))) + blender_mesh.use_auto_smooth = True + + + blender_mesh_object = bpy.data.objects.new(model.name, blender_mesh) + + + # VERTEX GROUPS + + vertex_groups_indicies = {} + + for offset in vertex_weights_offsets: + for i, weight_set in enumerate(vertex_weights_offsets[offset]): + for weight in weight_set: + index = weight.bone + + if index not in vertex_groups_indicies: + model_name = scene.models[index].name + vertex_groups_indicies[index] = blender_mesh_object.vertex_groups.new(name=model_name) + + vertex_groups_indicies[index].add([offset + i], weight.weight, 'ADD') + + + return blender_mesh_object + + + + + diff --git a/addons/io_scene_swbf_msh/msh_to_blend.py b/addons/io_scene_swbf_msh/msh_to_blend.py index 92753c1..2f9106f 100644 --- a/addons/io_scene_swbf_msh/msh_to_blend.py +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -13,6 +13,7 @@ from .msh_material_to_blend import * from .msh_model import * from .msh_skeleton_utilities import * from .msh_model_gather import get_is_model_hidden +from .msh_mesh_to_blend import model_to_mesh_object from .crc import * @@ -118,132 +119,13 @@ def extract_models(scene: Scene, materials_map : Dict[str, bpy.types.Material]) sorted_models : List[Model] = sort_by_parent(scene.models) for model in sorted_models: + new_obj = None - if model.model_type == ModelType.STATIC or model.model_type == ModelType.SKIN or model.model_type == ModelType.SHADOWVOLUME: - new_mesh = bpy.data.meshes.new(model.name) - verts = [] - faces = [] - offset = 0 + new_obj = model_to_mesh_object(model, scene, materials_map) - full_texcoords = [] - - weights_offsets = {} - - face_range_to_material_index = [] - - materials_to_use = [] - segment_index = 0 - - if model.geometry: - - def validate_segment(segment : GeometrySegment): - if not segment.positions: - return False - if not segment.triangles and not segment.triangle_strips and not segment.polygons: - return False - if not segment.material_name: - return False - return True - - - for seg in model.geometry: - - if not validate_segment(seg): - continue - - verts += [tuple(convert_vector_space(v)) for v in seg.positions] - - materials_to_use.append(seg.material_name) - - if seg.weights: - weights_offsets[offset] = seg.weights - - if seg.texcoords: - full_texcoords += seg.texcoords - else: - full_texcoords += [(0.0,0.0) for _ in range(len(seg.positions))] - - face_range_lower = len(faces) - - if seg.triangles: - faces += [tuple([ind + offset for ind in tri]) for tri in seg.triangles] - - elif seg.triangle_strips: - for strip in seg.triangle_strips: - for i in range(len(strip) - 2): - if i % 2 == 0: - face = tuple([offset + strip[j] for j in [i,i+1,i+2]]) - else: - face = tuple([offset + strip[j] for j in [i,i+2,i+1]]) - faces.append(face) - - elif seg.polygons: - faces += [tuple([ind + offset for ind in polygon]) for polygon in seg.polygons] - - - face_range_upper = len(faces) - face_range_to_material_index.append((face_range_lower, face_range_upper, segment_index)) - - offset += len(seg.positions) - - segment_index += 1 - - new_mesh.from_pydata(verts, [], faces) - new_mesh.update() - new_mesh.validate() - - - # If tex coords are present, add material and UV data - if full_texcoords: - - edit_mesh = bmesh.new() - edit_mesh.from_mesh(new_mesh) - - uvlayer = edit_mesh.loops.layers.uv.verify() - - for edit_mesh_face in edit_mesh.faces: - face_index = edit_mesh_face.index - mesh_face = faces[face_index] - - for frL, frU, ind in face_range_to_material_index: - if face_index >= frL and face_index < frU: - edit_mesh_face.material_index = ind - - for i,loop in enumerate(edit_mesh_face.loops): - texcoord = full_texcoords[mesh_face[i]] - loop[uvlayer].uv = tuple([texcoord[0], texcoord[1]]) - - edit_mesh.to_mesh(new_mesh) - edit_mesh.free() - - - new_obj = bpy.data.objects.new(new_mesh.name, new_mesh) - - - vertex_groups_indicies = {} - - for offset in weights_offsets: - for i, weight_set in enumerate(weights_offsets[offset]): - for weight in weight_set: - index = weight.bone - - if index not in vertex_groups_indicies: - model_name = scene.models[index].name - vertex_groups_indicies[index] = new_obj.vertex_groups.new(name=model_name) - - vertex_groups_indicies[index].add([offset + i], weight.weight, 'ADD') - - - ''' - Assign Material slots - ''' - for material_name in materials_to_use: - material = materials_map[material_name] - new_obj.data.materials.append(material) - else: new_obj = bpy.data.objects.new(model.name, None)