Added the ability to re-order materials when exporting multiple objects

This commit is contained in:
Colin Basnett
2024-05-27 20:51:17 -07:00
parent 2ff4a661f2
commit 972ea5deda
3 changed files with 132 additions and 56 deletions

View File

@@ -2,11 +2,9 @@ bl_info = {
'name': 'ASCII Scene Export (ASE)',
'description': 'Export ASE (ASCII Scene Export) files',
'author': 'Colin Basnett (Darklight Games)',
'version': (2, 0, 0),
'blender': (4, 0, 0),
'version': (2, 1, 0),
'blender': (4, 1, 0),
'location': 'File > Import-Export',
'warning': 'This add-on is under development.',
'wiki_url': 'https://github.com/DarklightGames/io_scene_ase/wiki',
'tracker_url': 'https://github.com/DarklightGames/io_scene_ase/issues',
'support': 'COMMUNITY',
'category': 'Import-Export'
@@ -26,26 +24,27 @@ from . import builder
from . import writer
from . import exporter
classes = (
exporter.ASE_OT_ExportOperator,
exporter.ASE_OT_ExportCollections,
)
classes = exporter.classes
def menu_func_export(self, context):
self.layout.operator(exporter.ASE_OT_ExportOperator.bl_idname, text='ASCII Scene Export (.ase)')
self.layout.operator(exporter.ASE_OT_ExportCollections.bl_idname, text='ASCII Scene Export Collections (.ase)')
self.layout.operator(exporter.ASE_OT_export.bl_idname, text='ASCII Scene Export (.ase)')
self.layout.operator(exporter.ASE_OT_export_collections.bl_idname, text='ASCII Scene Export Collections (.ase)')
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.ase_export = bpy.props.PointerProperty(type=exporter.ASE_PG_export)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
del bpy.types.Scene.ase_export
for cls in classes:
bpy.utils.unregister_class(cls)

View File

@@ -18,6 +18,7 @@ class ASEBuilderOptions(object):
def __init__(self):
self.scale = 1.0
self.use_raw_mesh_data = False
self.materials = []
class ASEBuilder(object):
@@ -29,6 +30,9 @@ class ASEBuilder(object):
mesh_objects = [obj for obj in objects if obj.type == 'MESH']
context.window_manager.progress_begin(0, len(mesh_objects))
for material in options.materials:
ase.materials.append(material)
for object_index, selected_object in enumerate(mesh_objects):
# Evaluate the mesh after modifiers are applied
if options.use_raw_mesh_data:
@@ -78,13 +82,7 @@ class ASEBuilder(object):
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 \'{selected_object.name}\' cannot be empty')
try:
# Reuse existing material entries for duplicates
material_index = ase.materials.index(material.name)
except ValueError:
material_index = len(ase.materials)
ase.materials.append(material.name)
material_indices.append(material_index)
material_indices.append(ase.materials.index(material))
mesh_data.calc_loop_triangles()

View File

@@ -1,47 +1,126 @@
import os.path
import typing
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, EnumProperty, BoolProperty
from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty
from bpy.types import Operator, Material, PropertyGroup, UIList
from .builder import *
from .writer import *
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
class ASE_PG_material(PropertyGroup):
material: PointerProperty(type=Material)
class ASE_PG_export(PropertyGroup):
material_list: CollectionProperty(name='Materials', type=ASE_PG_material)
material_list_index: IntProperty(name='Index', default=0)
def populate_material_list(mesh_objects, material_list):
material_list.clear()
materials = []
for mesh_object in mesh_objects:
for i, material_slot in enumerate(mesh_object.material_slots):
material = material_slot.material
if material is None:
raise RuntimeError('Material slot cannot be empty (index ' + str(i) + ')')
if material not in materials:
materials.append(material)
for index, material in enumerate(materials):
m = material_list.add()
m.material = material
m.index = index
class ASE_OT_material_list_move_up(Operator):
bl_idname = 'ase_export.material_list_item_move_up'
bl_label = 'Move Up'
bl_options = {'INTERNAL'}
bl_description = 'Move the selected material up one slot'
@classmethod
def poll(cls, context):
pg = getattr(context.scene, 'ase_export')
return pg.material_list_index > 0
def execute(self, context):
pg = getattr(context.scene, 'ase_export')
pg.material_list.move(pg.material_list_index, pg.material_list_index - 1)
pg.material_list_index -= 1
return {'FINISHED'}
class ASE_OT_material_list_move_down(Operator):
bl_idname = 'ase_export.material_list_item_move_down'
bl_label = 'Move Down'
bl_options = {'INTERNAL'}
bl_description = 'Move the selected material down one slot'
@classmethod
def poll(cls, context):
pg = getattr(context.scene, 'ase_export')
return pg.material_list_index < len(pg.material_list) - 1
def execute(self, context):
pg = getattr(context.scene, 'ase_export')
pg.material_list.move(pg.material_list_index, pg.material_list_index + 1)
pg.material_list_index += 1
return {'FINISHED'}
class ASE_UL_materials(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
row = layout.row()
row.prop(item.material, 'name', text='', emboss=False, icon_value=layout.icon(item.material))
class ASE_OT_export(Operator, ExportHelper):
bl_idname = 'io_scene_ase.ase_export'
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
}
filter_glob: StringProperty(default="*.ase", options={'HIDDEN'}, maxlen=255)
use_raw_mesh_data: BoolProperty(default=False, name='Raw Mesh Data', description='No modifiers will be evaluated as part of the exported mesh')
def draw(self, context):
layout = self.layout
layout.prop(self, 'units', expand=False)
layout.prop(self, 'use_raw_mesh_data')
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
materials_header.label(text='Materials')
if materials_panel:
row = materials_panel.row()
row.template_list('ASE_UL_materials', '', context.scene.ase_export, 'material_list', context.scene.ase_export, 'material_list_index')
col = row.column(align=True)
col.operator(ASE_OT_material_list_move_up.bl_idname, icon='TRIA_UP', text='')
col.operator(ASE_OT_material_list_move_down.bl_idname, icon='TRIA_DOWN', text='')
advanced_header, advanced_panel = layout.panel('Advanced', default_closed=True)
advanced_header.label(text='Advanced')
if advanced_panel:
advanced_panel.prop(self, 'use_raw_mesh_data')
def invoke(self, context: 'Context', event: 'Event' ) -> typing.Union[typing.Set[str], typing.Set[int]]:
mesh_objects = [x for x in context.selected_objects if x.type == 'MESH']
pg = getattr(context.scene, 'ase_export')
populate_material_list(mesh_objects, pg.material_list)
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
options = ASEBuilderOptions()
options.scale = self.units_scale[self.units]
options.scale = 1.0
options.use_raw_mesh_data = self.use_raw_mesh_data
pg = getattr(context.scene, 'ase_export')
options.materials = [x.material for x in pg.material_list]
try:
ase = ASEBuilder().build(context, options, context.selected_objects)
ASEWriter().write(self.filepath, ase)
@@ -52,7 +131,7 @@ class ASE_OT_ExportOperator(Operator, ExportHelper):
return {'CANCELLED'}
class ASE_OT_ExportCollections(Operator, ExportHelper):
class ASE_OT_export_collections(Operator, ExportHelper):
bl_idname = 'io_scene_ase.ase_export_collections' # important since its how bpy.ops.import_test.some_data is constructed
bl_label = 'Export Collections to ASE'
bl_space_type = 'PROPERTIES'
@@ -63,29 +142,18 @@ class ASE_OT_ExportCollections(Operator, ExportHelper):
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
}
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.scale = 1.0
options.use_raw_mesh_data = self.use_raw_mesh_data
# Iterate over all the visible collections in the scene.
@@ -111,3 +179,14 @@ class ASE_OT_ExportCollections(Operator, ExportHelper):
self.report({'INFO'}, f'{len(collections)} collections exported successfully')
return {'FINISHED'}
classes = (
ASE_PG_material,
ASE_UL_materials,
ASE_PG_export,
ASE_OT_export,
ASE_OT_export_collections,
ASE_OT_material_list_move_down,
ASE_OT_material_list_move_up,
)