4 Commits

Author SHA1 Message Date
Colin Basnett
e2e3905e2e Fixed a bug where collision meshes would not properly be exported in WYSIWYG mode 2022-05-20 16:40:55 -07:00
Colin Basnett
77f5aa21a5 Removed defunct comment 2022-05-16 17:29:10 -07:00
Colin Basnett
92f588b760 Initial commit for WYSIWYG export. Modifiers are automatically applied. 2022-05-11 17:12:29 -07:00
Colin Basnett
2b467b2da4 Minor formatting fix and comments 2022-05-01 02:05:48 -07:00
2 changed files with 39 additions and 22 deletions

View File

@@ -12,6 +12,7 @@ class ASEBuilderError(Exception):
class ASEBuilderOptions(object): class ASEBuilderOptions(object):
def __init__(self): def __init__(self):
self.scale = 1.0 self.scale = 1.0
self.use_raw_mesh_data = False
class ASEBuilder(object): class ASEBuilder(object):
@@ -19,42 +20,57 @@ class ASEBuilder(object):
ase = ASE() ase = ASE()
main_geometry_object = None main_geometry_object = None
for obj in context.selected_objects: for selected_object in context.selected_objects:
if obj is None or obj.type != 'MESH': if selected_object is None or selected_object.type != 'MESH':
continue continue
mesh_data = obj.data # Evaluate the mesh after modifiers are applied
if options.use_raw_mesh_data:
mesh_object = selected_object
mesh_data = mesh_object.data
else:
depsgraph = context.evaluated_depsgraph_get()
bm = bmesh.new()
bm.from_object(selected_object, depsgraph)
mesh_data = bpy.data.meshes.new('')
bm.to_mesh(mesh_data)
del bm
mesh_object = bpy.data.objects.new('', mesh_data)
mesh_object.matrix_world = selected_object.matrix_world
if not is_collision_name(obj.name) and main_geometry_object is not None: if not is_collision_name(selected_object.name) and main_geometry_object is not None:
geometry_object = main_geometry_object geometry_object = main_geometry_object
else: else:
geometry_object = ASEGeometryObject() geometry_object = ASEGeometryObject()
geometry_object.name = obj.name geometry_object.name = selected_object.name
if not geometry_object.is_collision: if not geometry_object.is_collision:
main_geometry_object = geometry_object main_geometry_object = geometry_object
ase.geometry_objects.append(geometry_object) ase.geometry_objects.append(geometry_object)
if geometry_object.is_collision: if geometry_object.is_collision:
# Test that collision meshes are manifold and convex.
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(obj.data) bm.from_mesh(mesh_object.data)
for edge in bm.edges: for edge in bm.edges:
if not edge.is_manifold: if not edge.is_manifold:
raise ASEBuilderError(f'Collision mesh \'{obj.name}\' is not manifold') del bm
raise ASEBuilderError(f'Collision mesh \'{selected_object.name}\' is not manifold')
if not edge.is_convex: if not edge.is_convex:
raise ASEBuilderError(f'Collision mesh \'{obj.name}\' is not convex') del bm
raise ASEBuilderError(f'Collision mesh \'{selected_object.name}\' is not convex')
if not geometry_object.is_collision and len(mesh_data.materials) == 0: if not geometry_object.is_collision and len(selected_object.data.materials) == 0:
raise ASEBuilderError(f'Mesh \'{obj.name}\' must have at least one material') raise ASEBuilderError(f'Mesh \'{selected_object.name}\' must have at least one material')
vertex_transform = Matrix.Scale(options.scale, 4) @ Matrix.Rotation(math.pi, 4, 'Z') @ obj.matrix_world vertex_transform = Matrix.Scale(options.scale, 4) @ Matrix.Rotation(math.pi, 4, 'Z') @ mesh_object.matrix_world
for vertex_index, vertex in enumerate(mesh_data.vertices): for vertex_index, vertex in enumerate(mesh_data.vertices):
geometry_object.vertices.append(vertex_transform @ vertex.co) geometry_object.vertices.append(vertex_transform @ vertex.co)
material_indices = [] material_indices = []
if not geometry_object.is_collision: if not geometry_object.is_collision:
for mesh_material_index, material in enumerate(mesh_data.materials): for mesh_material_index, material in enumerate(selected_object.data.materials):
if material is None: if material is None:
raise ASEBuilderError(f'Material slot {mesh_material_index + 1} for mesh \'{obj.name}\' cannot be empty') raise ASEBuilderError(f'Material slot {mesh_material_index + 1} for mesh \'{selected_object.name}\' cannot be empty')
try: try:
# Reuse existing material entries for duplicates # Reuse existing material entries for duplicates
material_index = ase.materials.index(material.name) material_index = ase.materials.index(material.name)
@@ -85,8 +101,8 @@ class ASEBuilder(object):
face.smoothing = (poly_groups[loop_triangle.polygon_index] - 1) % 32 face.smoothing = (poly_groups[loop_triangle.polygon_index] - 1) % 32
geometry_object.faces.append(face) geometry_object.faces.append(face)
# Normals
if not geometry_object.is_collision: if not geometry_object.is_collision:
# Normals
for face_index, loop_triangle in enumerate(mesh_data.loop_triangles): for face_index, loop_triangle in enumerate(mesh_data.loop_triangles):
face_normal = ASEFaceNormal() face_normal = ASEFaceNormal()
face_normal.normal = loop_triangle.normal face_normal.normal = loop_triangle.normal
@@ -98,7 +114,6 @@ class ASEBuilder(object):
face_normal.vertex_normals.append(vertex_normal) face_normal.vertex_normals.append(vertex_normal)
geometry_object.face_normals.append(face_normal) geometry_object.face_normals.append(face_normal)
if not geometry_object.is_collision:
# Texture Coordinates # Texture Coordinates
for i, uv_layer_data in enumerate([x.data for x in mesh_data.uv_layers]): for i, uv_layer_data in enumerate([x.data for x in mesh_data.uv_layers]):
if i >= len(geometry_object.uv_layers): if i >= len(geometry_object.uv_layers):
@@ -108,8 +123,7 @@ class ASEBuilder(object):
u, v = uv_layer_data[loop_index].uv u, v = uv_layer_data[loop_index].uv
uv_layer.texture_vertices.append((u, v, 0.0)) uv_layer.texture_vertices.append((u, v, 0.0))
# Texture Faces # Texture Faces
if not geometry_object.is_collision:
for loop_triangle in mesh_data.loop_triangles: for loop_triangle in mesh_data.loop_triangles:
geometry_object.texture_vertex_faces.append(( geometry_object.texture_vertex_faces.append((
geometry_object.texture_vertex_offset + loop_triangle.loops[0], geometry_object.texture_vertex_offset + loop_triangle.loops[0],
@@ -117,11 +131,11 @@ class ASEBuilder(object):
geometry_object.texture_vertex_offset + loop_triangle.loops[2] geometry_object.texture_vertex_offset + loop_triangle.loops[2]
)) ))
# Vertex Colors # Vertex Colors
if len(mesh_data.vertex_colors) > 0: if len(mesh_data.vertex_colors) > 0:
vertex_colors = mesh_data.vertex_colors.active.data vertex_colors = mesh_data.vertex_colors.active.data
for color in map(lambda x: x.color, vertex_colors): for color in map(lambda x: x.color, vertex_colors):
geometry_object.vertex_colors.append(tuple(color[0:3])) geometry_object.vertex_colors.append(tuple(color[0:3]))
# Update data offsets for next iteration # Update data offsets for next iteration
geometry_object.texture_vertex_offset = len(mesh_data.loops) geometry_object.texture_vertex_offset = len(mesh_data.loops)

View File

@@ -24,6 +24,7 @@ class ASE_OT_ExportOperator(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
('U', 'Unreal', '')), ('U', 'Unreal', '')),
name='Units' name='Units'
) )
use_raw_mesh_data: BoolProperty(default=False, name='Raw Mesh Data')
units_scale = { units_scale = {
'M': 60.352, 'M': 60.352,
@@ -33,10 +34,12 @@ class ASE_OT_ExportOperator(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, 'units', expand=False) layout.prop(self, 'units', expand=False)
layout.prop(self, 'use_raw_mesh_data')
def execute(self, context): def execute(self, context):
options = ASEBuilderOptions() options = ASEBuilderOptions()
options.scale = self.units_scale[self.units] options.scale = self.units_scale[self.units]
options.use_raw_mesh_data = self.use_raw_mesh_data
try: try:
ase = ASEBuilder().build(context, options) ase = ASEBuilder().build(context, options)
ASEWriter().write(self.filepath, ase) ASEWriter().write(self.filepath, ase)