8 Commits
1.0.1 ... 1.1.1

Author SHA1 Message Date
Colin Basnett
4b6231a094 Incremented version to 1.1.1 2022-02-09 14:30:15 -08:00
Colin Basnett
50a7d003cb Added manifold and convexity tests for collision objects. You will now be unable to export a concave or non-manifold collision shapes. 2022-02-08 21:42:26 -08:00
Colin Basnett
bd1a7257fd Added support for vertex colors. 2022-01-22 14:42:57 -08:00
Colin Basnett
7ebeb0d262 Added support for exporting multiple UV layers. 2021-12-27 02:07:50 -08:00
Colin Basnett
8edfac9bfb Fixed a bug where exporting more than two mesh objects would result in a corrupted ASE file. 2021-12-16 15:28:52 -08:00
Colin Basnett
1121b18fcb Incremented version. 2021-08-23 23:24:02 -07:00
Colin Basnett
dcd8c3ea65 Fix for bug #2 (missing unit settings on export in Blender 2.93) 2021-08-23 23:22:37 -07:00
Colin Basnett
283a44aec5 Removed zip file from the repository 🤦 2021-08-13 12:54:05 -07:00
7 changed files with 71 additions and 26 deletions

View File

@@ -3,3 +3,6 @@
This is a Blender addon allowing you to export static meshes to the now-defunct ASE (ASCII Scene Export) format still in use in legacy programs like Unreal Tournament 2004. This is a Blender addon allowing you to export static meshes to the now-defunct ASE (ASCII Scene Export) format still in use in legacy programs like Unreal Tournament 2004.
Check out [this video](https://www.youtube.com/watch?v=gpmBxCGHQjU) on how to install and use the addon. Check out [this video](https://www.youtube.com/watch?v=gpmBxCGHQjU) on how to install and use the addon.
Resources:
* https://wiki.beyondunreal.com/Legacy:ASE_File_Format

View File

@@ -2,7 +2,7 @@ bl_info = {
'name': 'ASCII Scene Export', 'name': 'ASCII Scene Export',
'description': 'Export ASE (ASCII Scene Export) files', 'description': 'Export ASE (ASCII Scene Export) files',
'author': 'Colin Basnett (Darklight Games)', 'author': 'Colin Basnett (Darklight Games)',
'version': (1, 0, 1), 'version': (1, 1, 2),
'blender': (2, 90, 0), 'blender': (2, 90, 0),
'location': 'File > Import-Export', 'location': 'File > Import-Export',
'warning': 'This add-on is under development.', 'warning': 'This add-on is under development.',
@@ -21,8 +21,6 @@ if 'bpy' in locals():
import bpy import bpy
import bpy.utils.previews import bpy.utils.previews
from bpy.props import IntProperty, CollectionProperty, StringProperty
import os
from . import ase from . import ase
from . import builder from . import builder
from . import writer from . import writer

View File

@@ -26,14 +26,20 @@ def is_collision_name(name):
return name.startswith('MCDCX_') return name.startswith('MCDCX_')
class ASEUVLayer(object):
def __init__(self):
self.texture_vertices = []
class ASEGeometryObject(object): class ASEGeometryObject(object):
def __init__(self): def __init__(self):
self.name = '' self.name = ''
self.vertices = [] self.vertices = []
self.texture_vertices = [] self.uv_layers = []
self.faces = [] self.faces = []
self.texture_vertex_faces = [] self.texture_vertex_faces = []
self.face_normals = [] self.face_normals = []
self.vertex_colors = []
self.vertex_offset = 0 self.vertex_offset = 0
self.texture_vertex_offset = 0 self.texture_vertex_offset = 0

View File

@@ -34,10 +34,18 @@ class ASEBuilder(object):
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:
bm = bmesh.new()
bm.from_mesh(obj.data)
for edge in bm.edges:
if not edge.is_manifold:
raise ASEBuilderError(f'Collision mesh \'{obj.name}\' is not manifold')
if not edge.is_convex:
raise ASEBuilderError(f'Collision mesh \'{obj.name}\' is not convex')
if not geometry_object.is_collision and len(mesh_data.materials) == 0: if not geometry_object.is_collision and len(mesh_data.materials) == 0:
raise ASEBuilderError(f'Mesh \'{obj.name}\' must have at least one material') raise ASEBuilderError(f'Mesh \'{obj.name}\' must have at least one material')
geometry_object.vertex_offset += len(geometry_object.vertices)
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') @ obj.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)
@@ -69,6 +77,11 @@ class ASEBuilder(object):
face.material_index = material_indices[loop_triangle.material_index] face.material_index = material_indices[loop_triangle.material_index]
# The UT2K4 importer only accepts 32 smoothing groups. Anything past this completely mangles the # The UT2K4 importer only accepts 32 smoothing groups. Anything past this completely mangles the
# smoothing groups and effectively makes the whole model use sharp-edge rendering. # smoothing groups and effectively makes the whole model use sharp-edge rendering.
# The fix is to constrain the smoothing group between 0 and 31 by applying a modulo of 32 to the actual
# smoothing group index.
# This may result in bad calculated normals on export in rare cases. For example, if a face with a
# smoothing group of 3 is adjacent to a face with a smoothing group of 35 (35 % 32 == 3), those faces
# will be treated as part of the same smoothing group.
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)
@@ -85,14 +98,15 @@ 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)
uv_layer = mesh_data.uv_layers.active.data
# Texture Coordinates
geometry_object.texture_vertex_offset += len(geometry_object.texture_vertices)
if not geometry_object.is_collision: if not geometry_object.is_collision:
for loop_index, loop in enumerate(mesh_data.loops): # Texture Coordinates
u, v = uv_layer[loop_index].uv for i, uv_layer_data in enumerate([x.data for x in mesh_data.uv_layers]):
geometry_object.texture_vertices.append((u, v, 0.0)) if i >= len(geometry_object.uv_layers):
geometry_object.uv_layers.append(ASEUVLayer())
uv_layer = geometry_object.uv_layers[i]
for loop_index, loop in enumerate(mesh_data.loops):
u, v = uv_layer_data[loop_index].uv
uv_layer.texture_vertices.append((u, v, 0.0))
# Texture Faces # Texture Faces
if not geometry_object.is_collision: if not geometry_object.is_collision:
@@ -103,6 +117,16 @@ class ASEBuilder(object):
geometry_object.texture_vertex_offset + loop_triangle.loops[2] geometry_object.texture_vertex_offset + loop_triangle.loops[2]
)) ))
# Vertex Colors
if len(mesh_data.vertex_colors) > 0:
vertex_colors = mesh_data.vertex_colors.active.data
for color in map(lambda x: x.color, vertex_colors):
geometry_object.vertex_colors.append(tuple(color[0:3]))
# Update data offsets for next iteration
geometry_object.texture_vertex_offset = len(mesh_data.loops)
geometry_object.vertex_offset = len(geometry_object.vertices)
if len(ase.geometry_objects) == 0: if len(ase.geometry_objects) == 0:
raise ASEBuilderError('At least one mesh object must be selected') raise ASEBuilderError('At least one mesh object must be selected')

View File

@@ -1,6 +1,6 @@
import bpy import bpy
import bpy_extras import bpy_extras
from bpy.props import StringProperty, FloatProperty, EnumProperty from bpy.props import StringProperty, FloatProperty, EnumProperty, BoolProperty
from .builder import * from .builder import *
from .writer import * from .writer import *
@@ -19,7 +19,7 @@ class ASE_OT_ExportOperator(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
maxlen=255, # Max internal buffer length, longer would be hilighted. maxlen=255, # Max internal buffer length, longer would be hilighted.
) )
units = EnumProperty( units: EnumProperty(
items=(('M', 'Meters', ''), items=(('M', 'Meters', ''),
('U', 'Unreal', '')), ('U', 'Unreal', '')),
name='Units' name='Units'

Binary file not shown.

View File

@@ -146,21 +146,23 @@ class ASEWriter(object):
face_node.push_sub_command('MESH_MTLID').push_datum(face.material_index) face_node.push_sub_command('MESH_MTLID').push_datum(face.material_index)
# Texture Coordinates # Texture Coordinates
if len(geometry_object.texture_vertices) > 0: for i, uv_layer in enumerate(geometry_object.uv_layers):
mesh_node.push_child('MESH_NUMTVERTEX').push_datum(len(geometry_object.texture_vertices)) parent_node = mesh_node if i == 0 else mesh_node.push_child('MESH_MAPPINGCHANNEL')
tvertlist_node = mesh_node.push_child('MESH_TVERTLIST') if i > 0:
for tvert_index, tvert in enumerate(geometry_object.texture_vertices): parent_node.push_datum(i + 1)
parent_node.push_child('MESH_NUMTVERTEX').push_datum(len(uv_layer.texture_vertices))
tvertlist_node = parent_node.push_child('MESH_TVERTLIST')
for tvert_index, tvert in enumerate(uv_layer.texture_vertices):
tvert_node = tvertlist_node.push_child('MESH_TVERT') tvert_node = tvertlist_node.push_child('MESH_TVERT')
tvert_node.push_datum(tvert_index) tvert_node.push_datum(tvert_index)
tvert_node.push_data(list(tvert)) tvert_node.push_data(list(tvert))
# Texture Faces
# Texture Faces if len(geometry_object.texture_vertex_faces) > 0:
if len(geometry_object.texture_vertex_faces) > 0: parent_node.push_child('MESH_NUMTVFACES').push_datum(len(geometry_object.texture_vertex_faces))
mesh_node.push_child('MESH_NUMTVFACES').push_datum(len(geometry_object.texture_vertex_faces)) texture_faces_node = parent_node.push_child('MESH_TFACELIST')
texture_faces_node = mesh_node.push_child('MESH_TFACELIST') for texture_face_index, texture_face in enumerate(geometry_object.texture_vertex_faces):
for texture_face_index, texture_face in enumerate(geometry_object.texture_vertex_faces): texture_face_node = texture_faces_node.push_child('MESH_TFACE')
texture_face_node = texture_faces_node.push_child('MESH_TFACE') texture_face_node.push_data([texture_face_index] + list(texture_face))
texture_face_node.push_data([texture_face_index] + list(texture_face))
# Normals # Normals
if len(geometry_object.face_normals) > 0: if len(geometry_object.face_normals) > 0:
@@ -174,6 +176,18 @@ class ASEWriter(object):
vertex_normal_node.push_datum(vertex_normal.vertex_index) vertex_normal_node.push_datum(vertex_normal.vertex_index)
vertex_normal_node.push_data(list(vertex_normal.normal)) vertex_normal_node.push_data(list(vertex_normal.normal))
# Vertex Colors
if len(geometry_object.vertex_colors) > 0:
mesh_node.push_child('MESH_NUMCVERTEX').push_datum(len(geometry_object.vertex_colors))
cvert_list = mesh_node.push_child('MESH_CVERTLIST')
for i, vertex_color in enumerate(geometry_object.vertex_colors):
cvert_list.push_child('MESH_VERTCOL').push_datum(i).push_data(vertex_color)
parent_node.push_child('MESH_NUMCVFACES').push_datum(len(geometry_object.texture_vertex_faces))
texture_faces_node = parent_node.push_child('MESH_CFACELIST')
for texture_face_index, texture_face in enumerate(geometry_object.texture_vertex_faces):
texture_face_node = texture_faces_node.push_child('MESH_CFACE')
texture_face_node.push_data([texture_face_index] + list(texture_face))
geomobject_node.push_child('MATERIAL_REF').push_datum(0) geomobject_node.push_child('MATERIAL_REF').push_datum(0)
return root return root