Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce6ae4a64c | ||
|
|
a593224b14 | ||
|
|
feb88794b2 | ||
|
|
499e6f19c6 | ||
|
|
1bdc158f08 | ||
|
|
e2e3905e2e | ||
|
|
77f5aa21a5 | ||
|
|
92f588b760 | ||
|
|
2b467b2da4 | ||
|
|
4b6231a094 | ||
|
|
50a7d003cb | ||
|
|
bd1a7257fd |
@@ -1,8 +1,8 @@
|
||||
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),
|
||||
'version': (1, 2, 0),
|
||||
'blender': (2, 90, 0),
|
||||
'location': 'File > Import-Export',
|
||||
'warning': 'This add-on is under development.',
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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,6 +131,12 @@ class ASEBuilder(object):
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
import bpy_extras
|
||||
from bpy.props import StringProperty, FloatProperty, EnumProperty
|
||||
from bpy.props import StringProperty, FloatProperty, EnumProperty, BoolProperty
|
||||
from .builder import *
|
||||
from .writer import *
|
||||
|
||||
@@ -10,21 +10,22 @@ class ASE_OT_ExportOperator(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user