18 Commits
1.1.0 ... 2.0.0

Author SHA1 Message Date
Colin Basnett
ecfd9897b1 Moved files to named subfolder for easier packaging 2024-02-28 18:50:12 -08:00
Colin Basnett
a3e350e96e Fix for rare error if vertex colors were somehow not active 2024-02-28 18:49:00 -08:00
Colin Basnett
7b417ae425 Incremented version to 2.0.0 and the minimum version to 4.0.0 2023-11-10 23:29:45 -08:00
Colin Basnett
c59f16ed5e Removed deprecated call to calc_normals_split 2023-11-10 23:28:30 -08:00
Colin Basnett
51be4b8ee9 Removed zip file committed in error 2022-08-15 21:53:24 -07:00
Colin Basnett
94b3554f2f Fixed multi-object export texture corruption error
This fixes the bug where exporting 3 or more objects would result in
one or more objects have corrupted texture coordinates
2022-08-15 21:39:13 -07:00
Colin Basnett
ce6ae4a64c Added description to raw mesh data option 2022-08-11 02:09:27 -07:00
Colin Basnett
a593224b14 Added (ASE) to the name of the addon
This should make it easier to find in the addon list when searching
2022-08-11 02:08:26 -07:00
Colin Basnett
feb88794b2 Incremented version to 1.2.0 2022-08-11 02:07:38 -07:00
Colin Basnett
499e6f19c6 The default units are now Unreal instead of Meters 2022-08-11 02:06:46 -07:00
Colin Basnett
1bdc158f08 Merge branch 'feature-wysiwyg' 2022-08-11 00:02:10 -07:00
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
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
5 changed files with 73 additions and 29 deletions

View File

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

View File

@@ -39,6 +39,7 @@ class ASEGeometryObject(object):
self.faces = []
self.texture_vertex_faces = []
self.face_normals = []
self.vertex_colors = []
self.vertex_offset = 0
self.texture_vertex_offset = 0

View File

@@ -12,6 +12,7 @@ class ASEBuilderError(Exception):
class ASEBuilderOptions(object):
def __init__(self):
self.scale = 1.0
self.use_raw_mesh_data = False
class ASEBuilder(object):
@@ -19,33 +20,57 @@ class ASEBuilder(object):
ase = ASE()
main_geometry_object = None
for obj in context.selected_objects:
if obj is None or obj.type != 'MESH':
for selected_object in context.selected_objects:
if selected_object is None or selected_object.type != 'MESH':
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
else:
geometry_object = ASEGeometryObject()
geometry_object.name = obj.name
geometry_object.name = selected_object.name
if not geometry_object.is_collision:
main_geometry_object = geometry_object
ase.geometry_objects.append(geometry_object)
if not geometry_object.is_collision and len(mesh_data.materials) == 0:
raise ASEBuilderError(f'Mesh \'{obj.name}\' must have at least one material')
if geometry_object.is_collision:
# Test that collision meshes are manifold and convex.
bm = bmesh.new()
bm.from_mesh(mesh_object.data)
for edge in bm.edges:
if not edge.is_manifold:
del bm
raise ASEBuilderError(f'Collision mesh \'{selected_object.name}\' is not manifold')
if not edge.is_convex:
del bm
raise ASEBuilderError(f'Collision mesh \'{selected_object.name}\' is not convex')
vertex_transform = Matrix.Scale(options.scale, 4) @ Matrix.Rotation(math.pi, 4, 'Z') @ obj.matrix_world
if not geometry_object.is_collision and len(selected_object.data.materials) == 0:
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') @ mesh_object.matrix_world
for vertex_index, vertex in enumerate(mesh_data.vertices):
geometry_object.vertices.append(vertex_transform @ vertex.co)
material_indices = []
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:
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:
# Reuse existing material entries for duplicates
material_index = ase.materials.index(material.name)
@@ -55,7 +80,7 @@ class ASEBuilder(object):
material_indices.append(material_index)
mesh_data.calc_loop_triangles()
mesh_data.calc_normals_split()
poly_groups, groups = mesh_data.calc_smooth_groups(use_bitflags=False)
# Faces
@@ -76,8 +101,8 @@ class ASEBuilder(object):
face.smoothing = (poly_groups[loop_triangle.polygon_index] - 1) % 32
geometry_object.faces.append(face)
# Normals
if not geometry_object.is_collision:
# Normals
for face_index, loop_triangle in enumerate(mesh_data.loop_triangles):
face_normal = ASEFaceNormal()
face_normal.normal = loop_triangle.normal
@@ -89,7 +114,6 @@ class ASEBuilder(object):
face_normal.vertex_normals.append(vertex_normal)
geometry_object.face_normals.append(face_normal)
if not geometry_object.is_collision:
# Texture Coordinates
for i, uv_layer_data in enumerate([x.data for x in mesh_data.uv_layers]):
if i >= len(geometry_object.uv_layers):
@@ -99,8 +123,7 @@ class ASEBuilder(object):
u, v = uv_layer_data[loop_index].uv
uv_layer.texture_vertices.append((u, v, 0.0))
# Texture Faces
if not geometry_object.is_collision:
# Texture Faces
for loop_triangle in mesh_data.loop_triangles:
geometry_object.texture_vertex_faces.append((
geometry_object.texture_vertex_offset + loop_triangle.loops[0],
@@ -108,8 +131,15 @@ class ASEBuilder(object):
geometry_object.texture_vertex_offset + loop_triangle.loops[2]
))
# Vertex Colors
if len(mesh_data.vertex_colors) > 0:
if mesh_data.vertex_colors.active is not None:
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.texture_vertex_offset += len(mesh_data.loops)
geometry_object.vertex_offset = len(geometry_object.vertices)
if len(ase.geometry_objects) == 0:

View File

@@ -1,30 +1,31 @@
import bpy
import bpy_extras
from bpy.props import StringProperty, FloatProperty, EnumProperty
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, EnumProperty, BoolProperty
from bpy.types import Operator
from .builder import *
from .writer import *
class ASE_OT_ExportOperator(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
class ASE_OT_ExportOperator(Operator, ExportHelper):
bl_idname = 'io_scene_ase.ase_export' # important since its how bpy.ops.import_test.some_data is constructed
bl_label = 'Export ASE'
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
filename_ext = '.ase'
filter_glob: StringProperty(
default="*.ase",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be hilighted.
)
units: EnumProperty(
default='U',
items=(('M', 'Meters', ''),
('U', 'Unreal', '')),
name='Units'
)
use_raw_mesh_data: BoolProperty(
default=False,
description='No modifiers will be evaluated as part of the exported mesh',
name='Raw Mesh Data')
units_scale = {
'M': 60.352,
'U': 1.0
@@ -33,10 +34,12 @@ class ASE_OT_ExportOperator(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
def draw(self, context):
layout = self.layout
layout.prop(self, 'units', expand=False)
layout.prop(self, 'use_raw_mesh_data')
def execute(self, context):
options = ASEBuilderOptions()
options.scale = self.units_scale[self.units]
options.use_raw_mesh_data = self.use_raw_mesh_data
try:
ase = ASEBuilder().build(context, options)
ASEWriter().write(self.filepath, ase)

View File

@@ -176,6 +176,18 @@ class ASEWriter(object):
vertex_normal_node.push_datum(vertex_normal.vertex_index)
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)
return root