Fixed vertex color export & added "invert normals" option
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
from bpy.types import Material
|
||||||
|
|
||||||
|
|
||||||
class ASEFace(object):
|
class ASEFace(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.a = 0
|
self.a = 0
|
||||||
@@ -50,6 +55,5 @@ class ASEGeometryObject(object):
|
|||||||
|
|
||||||
class ASE(object):
|
class ASE(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.materials = []
|
self.materials: List[Optional[Material]] = []
|
||||||
self.geometry_objects = []
|
self.geometry_objects = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Iterable, Optional, List, Tuple
|
from typing import Iterable, Optional, List, Tuple
|
||||||
|
|
||||||
from bpy.types import Object, Context, Material
|
from bpy.types import Object, Context, Material, Mesh
|
||||||
|
|
||||||
from .ase import ASE, ASEGeometryObject, ASEFace, ASEFaceNormal, ASEVertexNormal, ASEUVLayer, is_collision_name
|
from .ase import ASE, ASEGeometryObject, ASEFace, ASEFaceNormal, ASEVertexNormal, ASEUVLayer, is_collision_name
|
||||||
import bpy
|
import bpy
|
||||||
@@ -10,14 +10,20 @@ from mathutils import Matrix, Vector
|
|||||||
|
|
||||||
SMOOTHING_GROUP_MAX = 32
|
SMOOTHING_GROUP_MAX = 32
|
||||||
|
|
||||||
class ASEBuilderError(Exception):
|
class ASEBuildError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ASEBuilderOptions(object):
|
class ASEBuildOptions(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.object_eval_state = 'EVALUATED'
|
self.object_eval_state = 'EVALUATED'
|
||||||
self.materials: Optional[List[Material]] = None
|
self.materials: Optional[List[Material]] = None
|
||||||
|
self.transform = Matrix.Identity(4)
|
||||||
|
self.should_export_vertex_colors = True
|
||||||
|
self.vertex_color_mode = 'ACTIVE'
|
||||||
|
self.has_vertex_colors = False
|
||||||
|
self.vertex_color_attribute = ''
|
||||||
|
self.should_invert_normals = False
|
||||||
|
|
||||||
|
|
||||||
def get_object_matrix(obj: Object, asset_instance: Optional[Object] = None) -> Matrix:
|
def get_object_matrix(obj: Object, asset_instance: Optional[Object] = None) -> Matrix:
|
||||||
@@ -38,151 +44,165 @@ def get_mesh_objects(objects: Iterable[Object]) -> List[Tuple[Object, Optional[O
|
|||||||
return mesh_objects
|
return mesh_objects
|
||||||
|
|
||||||
|
|
||||||
|
def build_ase(context: Context, options: ASEBuildOptions, objects: Iterable[Object]) -> ASE:
|
||||||
|
ase = ASE()
|
||||||
|
|
||||||
class ASEBuilder(object):
|
main_geometry_object = None
|
||||||
def build(self, context: Context, options: ASEBuilderOptions, objects: Iterable[Object]):
|
mesh_objects = get_mesh_objects(objects)
|
||||||
ase = ASE()
|
|
||||||
|
|
||||||
main_geometry_object = None
|
context.window_manager.progress_begin(0, len(mesh_objects))
|
||||||
mesh_objects = get_mesh_objects(objects)
|
|
||||||
|
|
||||||
context.window_manager.progress_begin(0, len(mesh_objects))
|
ase.materials = options.materials
|
||||||
|
|
||||||
ase.materials = options.materials
|
for object_index, (obj, asset_instance) in enumerate(mesh_objects):
|
||||||
|
|
||||||
for object_index, (obj, asset_instance) in enumerate(mesh_objects):
|
matrix_world = get_object_matrix(obj, asset_instance)
|
||||||
|
|
||||||
matrix_world = get_object_matrix(obj, asset_instance)
|
# Save the active color name for vertex color export.
|
||||||
|
active_color_name = obj.data.color_attributes.active_color_name
|
||||||
|
|
||||||
# Evaluate the mesh after modifiers are applied
|
match options.object_eval_state:
|
||||||
match options.object_eval_state:
|
case 'ORIGINAL':
|
||||||
case 'ORIGINAL':
|
mesh_object = obj
|
||||||
mesh_object = obj
|
mesh_data = mesh_object.data
|
||||||
mesh_data = mesh_object.data
|
case 'EVALUATED':
|
||||||
case 'EVALUATED':
|
# Evaluate the mesh after modifiers are applied
|
||||||
depsgraph = context.evaluated_depsgraph_get()
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
bm = bmesh.new()
|
|
||||||
bm.from_object(obj, 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 = matrix_world
|
|
||||||
|
|
||||||
if not is_collision_name(obj.name) and main_geometry_object is not None:
|
|
||||||
geometry_object = main_geometry_object
|
|
||||||
else:
|
|
||||||
geometry_object = ASEGeometryObject()
|
|
||||||
geometry_object.name = obj.name
|
|
||||||
if not geometry_object.is_collision:
|
|
||||||
main_geometry_object = geometry_object
|
|
||||||
ase.geometry_objects.append(geometry_object)
|
|
||||||
|
|
||||||
if geometry_object.is_collision:
|
|
||||||
# Test that collision meshes are manifold and convex.
|
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
bm.from_mesh(mesh_object.data)
|
bm.from_object(obj, depsgraph)
|
||||||
for edge in bm.edges:
|
mesh_data = bpy.data.meshes.new('')
|
||||||
if not edge.is_manifold:
|
bm.to_mesh(mesh_data)
|
||||||
del bm
|
del bm
|
||||||
raise ASEBuilderError(f'Collision mesh \'{obj.name}\' is not manifold')
|
mesh_object = bpy.data.objects.new('', mesh_data)
|
||||||
if not edge.is_convex:
|
mesh_object.matrix_world = matrix_world
|
||||||
del bm
|
|
||||||
raise ASEBuilderError(f'Collision mesh \'{obj.name}\' is not convex')
|
|
||||||
|
|
||||||
if not geometry_object.is_collision and len(obj.data.materials) == 0:
|
if not is_collision_name(obj.name) and main_geometry_object is not None:
|
||||||
raise ASEBuilderError(f'Mesh \'{obj.name}\' must have at least one material')
|
geometry_object = main_geometry_object
|
||||||
|
else:
|
||||||
vertex_transform = Matrix.Rotation(math.pi, 4, 'Z') @ matrix_world
|
geometry_object = ASEGeometryObject()
|
||||||
|
geometry_object.name = obj.name
|
||||||
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:
|
if not geometry_object.is_collision:
|
||||||
for mesh_material_index, material in enumerate(obj.data.materials):
|
main_geometry_object = geometry_object
|
||||||
if material is None:
|
ase.geometry_objects.append(geometry_object)
|
||||||
raise ASEBuilderError(f'Material slot {mesh_material_index + 1} for mesh \'{obj.name}\' cannot be empty')
|
|
||||||
material_indices.append(ase.materials.index(material))
|
|
||||||
|
|
||||||
mesh_data.calc_loop_triangles()
|
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 ASEBuildError(f'Collision mesh \'{obj.name}\' is not manifold')
|
||||||
|
if not edge.is_convex:
|
||||||
|
del bm
|
||||||
|
raise ASEBuildError(f'Collision mesh \'{obj.name}\' is not convex')
|
||||||
|
|
||||||
# Calculate smoothing groups.
|
vertex_transform = Matrix.Rotation(math.pi, 4, 'Z') @ matrix_world
|
||||||
poly_groups, groups = mesh_data.calc_smooth_groups(use_bitflags=False)
|
|
||||||
|
|
||||||
# Figure out how many scaling axes are negative.
|
for vertex_index, vertex in enumerate(mesh_data.vertices):
|
||||||
# This is important for calculating the normals of the mesh.
|
geometry_object.vertices.append(vertex_transform @ vertex.co)
|
||||||
_, _, scale = vertex_transform.decompose()
|
|
||||||
negative_scaling_axes = sum([1 for x in scale if x < 0])
|
|
||||||
should_invert_normals = negative_scaling_axes % 2 == 1
|
|
||||||
|
|
||||||
loop_triangle_index_order = (2, 1, 0) if should_invert_normals else (0, 1, 2)
|
material_indices = []
|
||||||
|
if not geometry_object.is_collision:
|
||||||
|
for mesh_material_index, material in enumerate(obj.data.materials):
|
||||||
|
if material is None:
|
||||||
|
raise ASEBuildError(f'Material slot {mesh_material_index + 1} for mesh \'{obj.name}\' cannot be empty')
|
||||||
|
material_indices.append(ase.materials.index(material))
|
||||||
|
|
||||||
# Faces
|
if len(material_indices) == 0:
|
||||||
|
# If no materials are assigned to the mesh, just have a single empty material.
|
||||||
|
material_indices.append(0)
|
||||||
|
|
||||||
|
mesh_data.calc_loop_triangles()
|
||||||
|
|
||||||
|
# Calculate smoothing groups.
|
||||||
|
poly_groups, groups = mesh_data.calc_smooth_groups(use_bitflags=False)
|
||||||
|
|
||||||
|
# Figure out how many scaling axes are negative.
|
||||||
|
# This is important for calculating the normals of the mesh.
|
||||||
|
_, _, scale = vertex_transform.decompose()
|
||||||
|
negative_scaling_axes = sum([1 for x in scale if x < 0])
|
||||||
|
should_invert_normals = negative_scaling_axes % 2 == 1
|
||||||
|
if options.should_invert_normals:
|
||||||
|
should_invert_normals = not should_invert_normals
|
||||||
|
|
||||||
|
loop_triangle_index_order = (2, 1, 0) if should_invert_normals else (0, 1, 2)
|
||||||
|
|
||||||
|
# Faces
|
||||||
|
for face_index, loop_triangle in enumerate(mesh_data.loop_triangles):
|
||||||
|
face = ASEFace()
|
||||||
|
face.a, face.b, face.c = map(lambda j: geometry_object.vertex_offset + mesh_data.loops[loop_triangle.loops[j]].vertex_index, loop_triangle_index_order)
|
||||||
|
if not geometry_object.is_collision:
|
||||||
|
face.material_index = material_indices[loop_triangle.material_index]
|
||||||
|
# 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.
|
||||||
|
# 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) % SMOOTHING_GROUP_MAX
|
||||||
|
geometry_object.faces.append(face)
|
||||||
|
|
||||||
|
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 = ASEFace()
|
face_normal = ASEFaceNormal()
|
||||||
face.a, face.b, face.c = map(lambda j: geometry_object.vertex_offset + mesh_data.loops[loop_triangle.loops[j]].vertex_index, loop_triangle_index_order)
|
face_normal.normal = loop_triangle.normal
|
||||||
if not geometry_object.is_collision:
|
face_normal.vertex_normals = []
|
||||||
face.material_index = material_indices[loop_triangle.material_index]
|
for i in loop_triangle_index_order:
|
||||||
# The UT2K4 importer only accepts 32 smoothing groups. Anything past this completely mangles the
|
vertex_normal = ASEVertexNormal()
|
||||||
# smoothing groups and effectively makes the whole model use sharp-edge rendering.
|
vertex_normal.vertex_index = geometry_object.vertex_offset + mesh_data.loops[loop_triangle.loops[i]].vertex_index
|
||||||
# The fix is to constrain the smoothing group between 0 and 31 by applying a modulo of 32 to the actual
|
vertex_normal.normal = loop_triangle.split_normals[i]
|
||||||
# smoothing group index.
|
if should_invert_normals:
|
||||||
# This may result in bad calculated normals on export in rare cases. For example, if a face with a
|
vertex_normal.normal = (-Vector(vertex_normal.normal)).to_tuple()
|
||||||
# smoothing group of 3 is adjacent to a face with a smoothing group of 35 (35 % 32 == 3), those faces
|
face_normal.vertex_normals.append(vertex_normal)
|
||||||
# will be treated as part of the same smoothing group.
|
geometry_object.face_normals.append(face_normal)
|
||||||
face.smoothing = (poly_groups[loop_triangle.polygon_index] - 1) % SMOOTHING_GROUP_MAX
|
|
||||||
geometry_object.faces.append(face)
|
|
||||||
|
|
||||||
if not geometry_object.is_collision:
|
# Texture Coordinates
|
||||||
# Normals
|
for i, uv_layer_data in enumerate([x.data for x in mesh_data.uv_layers]):
|
||||||
for face_index, loop_triangle in enumerate(mesh_data.loop_triangles):
|
if i >= len(geometry_object.uv_layers):
|
||||||
face_normal = ASEFaceNormal()
|
geometry_object.uv_layers.append(ASEUVLayer())
|
||||||
face_normal.normal = loop_triangle.normal
|
uv_layer = geometry_object.uv_layers[i]
|
||||||
face_normal.vertex_normals = []
|
for loop_index, loop in enumerate(mesh_data.loops):
|
||||||
for i in loop_triangle_index_order:
|
u, v = uv_layer_data[loop_index].uv
|
||||||
vertex_normal = ASEVertexNormal()
|
uv_layer.texture_vertices.append((u, v, 0.0))
|
||||||
vertex_normal.vertex_index = geometry_object.vertex_offset + mesh_data.loops[loop_triangle.loops[i]].vertex_index
|
|
||||||
vertex_normal.normal = loop_triangle.split_normals[i]
|
|
||||||
if should_invert_normals:
|
|
||||||
vertex_normal.normal = (-Vector(vertex_normal.normal)).to_tuple()
|
|
||||||
face_normal.vertex_normals.append(vertex_normal)
|
|
||||||
geometry_object.face_normals.append(face_normal)
|
|
||||||
|
|
||||||
# Texture Coordinates
|
# Texture Faces
|
||||||
for i, uv_layer_data in enumerate([x.data for x in mesh_data.uv_layers]):
|
for loop_triangle in mesh_data.loop_triangles:
|
||||||
if i >= len(geometry_object.uv_layers):
|
geometry_object.texture_vertex_faces.append(
|
||||||
geometry_object.uv_layers.append(ASEUVLayer())
|
tuple(map(lambda l: geometry_object.texture_vertex_offset + loop_triangle.loops[l], loop_triangle_index_order))
|
||||||
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
|
# Vertex Colors
|
||||||
for loop_triangle in mesh_data.loop_triangles:
|
if options.should_export_vertex_colors and options.has_vertex_colors:
|
||||||
geometry_object.texture_vertex_faces.append(
|
color_attribute = None
|
||||||
tuple(map(lambda l: geometry_object.texture_vertex_offset + loop_triangle.loops[l], loop_triangle_index_order))
|
match options.vertex_color_mode:
|
||||||
)
|
case 'ACTIVE':
|
||||||
|
color_attribute = mesh_data.color_attributes[active_color_name]
|
||||||
|
case 'EXPLICIT':
|
||||||
|
color_attribute = mesh_data.color_attributes.get(options.vertex_color_attribute, None)
|
||||||
|
|
||||||
# Vertex Colors
|
if color_attribute is not None:
|
||||||
if len(mesh_data.vertex_colors) > 0:
|
# Make sure that the selected color attribute is on the CORNER domain.
|
||||||
if mesh_data.vertex_colors.active is not None:
|
if color_attribute.domain != 'CORNER':
|
||||||
vertex_colors = mesh_data.vertex_colors.active.data
|
raise ASEBuildError(f'Color attribute \'{color_attribute.name}\' for object \'{obj.name}\' must have domain of \'CORNER\' (found \'{color_attribute.domain}\')')
|
||||||
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
|
for color in map(lambda x: x.color, color_attribute.data):
|
||||||
geometry_object.texture_vertex_offset += len(mesh_data.loops)
|
geometry_object.vertex_colors.append(tuple(color[0:3]))
|
||||||
geometry_object.vertex_offset = len(geometry_object.vertices)
|
|
||||||
|
|
||||||
context.window_manager.progress_update(object_index)
|
# Update data offsets for next iteration
|
||||||
|
geometry_object.texture_vertex_offset += len(mesh_data.loops)
|
||||||
|
geometry_object.vertex_offset = len(geometry_object.vertices)
|
||||||
|
|
||||||
context.window_manager.progress_end()
|
context.window_manager.progress_update(object_index)
|
||||||
|
|
||||||
if len(ase.geometry_objects) == 0:
|
context.window_manager.progress_end()
|
||||||
raise ASEBuilderError('At least one mesh object must be selected')
|
|
||||||
|
|
||||||
if main_geometry_object is None:
|
if len(ase.geometry_objects) == 0:
|
||||||
raise ASEBuilderError('At least one non-collision mesh must be exported')
|
raise ASEBuildError('At least one mesh object must be selected')
|
||||||
|
|
||||||
return ase
|
if main_geometry_object is None:
|
||||||
|
raise ASEBuildError('At least one non-collision mesh must be exported')
|
||||||
|
|
||||||
|
return ase
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ from typing import Iterable, List, Set, Union
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy_extras.io_utils import ExportHelper
|
from bpy_extras.io_utils import ExportHelper
|
||||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty, EnumProperty
|
from bpy.props import StringProperty, CollectionProperty, PointerProperty, IntProperty, EnumProperty, BoolProperty
|
||||||
from bpy.types import Operator, Material, PropertyGroup, UIList, Object, FileHandler, Collection
|
from bpy.types import Operator, Material, PropertyGroup, UIList, Object, FileHandler
|
||||||
from .builder import ASEBuilder, ASEBuilderOptions, ASEBuilderError, get_mesh_objects
|
from mathutils import Matrix, Vector
|
||||||
|
|
||||||
|
from .builder import ASEBuildOptions, ASEBuildError, get_mesh_objects, build_ase
|
||||||
from .writer import ASEWriter
|
from .writer import ASEWriter
|
||||||
|
|
||||||
|
|
||||||
@@ -13,9 +15,35 @@ class ASE_PG_material(PropertyGroup):
|
|||||||
material: PointerProperty(type=Material)
|
material: PointerProperty(type=Material)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vertex_color_attributes_from_objects(objects: Iterable[Object]) -> Set[str]:
|
||||||
|
'''
|
||||||
|
Get the unique vertex color attributes from all the selected objects.
|
||||||
|
:param objects: The objects to search for vertex color attributes.
|
||||||
|
:return: A set of unique vertex color attributes.
|
||||||
|
'''
|
||||||
|
items = set()
|
||||||
|
for obj in filter(lambda x: x.type == 'MESH', objects):
|
||||||
|
for layer in filter(lambda x: x.domain == 'CORNER', obj.data.color_attributes):
|
||||||
|
items.add(layer.name)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def vertex_color_attribute_items(self, context):
|
||||||
|
# Get the unique color attributes from all the selected objects.
|
||||||
|
return [(x, x, '') for x in sorted(get_vertex_color_attributes_from_objects(context.selected_objects))]
|
||||||
|
|
||||||
|
|
||||||
class ASE_PG_export(PropertyGroup):
|
class ASE_PG_export(PropertyGroup):
|
||||||
material_list: CollectionProperty(name='Materials', type=ASE_PG_material)
|
material_list: CollectionProperty(name='Materials', type=ASE_PG_material)
|
||||||
material_list_index: IntProperty(name='Index', default=0)
|
material_list_index: IntProperty(name='Index', default=0)
|
||||||
|
should_export_vertex_colors: BoolProperty(name='Export Vertex Colors', default=True)
|
||||||
|
vertex_color_mode: EnumProperty(name='Vertex Color Mode', items=(
|
||||||
|
('ACTIVE', 'Active', 'Use the active vertex color attribute'),
|
||||||
|
('EXPLICIT', 'Explicit', 'Use the vertex color attribute specified below'),
|
||||||
|
))
|
||||||
|
has_vertex_colors: BoolProperty(name='Has Vertex Colors', default=False, options={'HIDDEN'})
|
||||||
|
vertex_color_attribute: EnumProperty(name='Attribute', items=vertex_color_attribute_items)
|
||||||
|
should_invert_normals: BoolProperty(name='Invert Normals', default=False, description='Invert the normals of the exported geometry. This should be used if the software you are exporting to uses a different winding order than Blender')
|
||||||
|
|
||||||
|
|
||||||
def get_unique_materials(mesh_objects: Iterable[Object]) -> List[Material]:
|
def get_unique_materials(mesh_objects: Iterable[Object]) -> List[Material]:
|
||||||
@@ -109,17 +137,35 @@ class ASE_OT_export(Operator, ExportHelper):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
pg = context.scene.ase_export
|
||||||
|
|
||||||
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
|
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
|
||||||
materials_header.label(text='Materials')
|
materials_header.label(text='Materials')
|
||||||
|
|
||||||
if materials_panel:
|
if materials_panel:
|
||||||
row = materials_panel.row()
|
row = materials_panel.row()
|
||||||
row.template_list('ASE_UL_materials', '', context.scene.ase_export, 'material_list', context.scene.ase_export, 'material_list_index')
|
row.template_list('ASE_UL_materials', '', pg, 'material_list', pg, 'material_list_index')
|
||||||
col = row.column(align=True)
|
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_up.bl_idname, icon='TRIA_UP', text='')
|
||||||
col.operator(ASE_OT_material_list_move_down.bl_idname, icon='TRIA_DOWN', text='')
|
col.operator(ASE_OT_material_list_move_down.bl_idname, icon='TRIA_DOWN', text='')
|
||||||
|
|
||||||
|
|
||||||
|
has_vertex_colors = len(get_vertex_color_attributes_from_objects(context.selected_objects)) > 0
|
||||||
|
vertex_colors_header, vertex_colors_panel = layout.panel_prop(pg, 'should_export_vertex_colors')
|
||||||
|
row = vertex_colors_header.row()
|
||||||
|
row.enabled = has_vertex_colors
|
||||||
|
row.prop(pg, 'should_export_vertex_colors', text='Vertex Colors')
|
||||||
|
|
||||||
|
if vertex_colors_panel:
|
||||||
|
vertex_colors_panel.use_property_split = True
|
||||||
|
vertex_colors_panel.use_property_decorate = False
|
||||||
|
if has_vertex_colors:
|
||||||
|
vertex_colors_panel.prop(pg, 'vertex_color_mode', text='Mode')
|
||||||
|
if pg.vertex_color_mode == 'EXPLICIT':
|
||||||
|
vertex_colors_panel.prop(pg, 'vertex_color_attribute', icon='GROUP_VCOL')
|
||||||
|
else:
|
||||||
|
vertex_colors_panel.label(text='No vertex color attributes found')
|
||||||
|
|
||||||
advanced_header, advanced_panel = layout.panel('Advanced', default_closed=True)
|
advanced_header, advanced_panel = layout.panel('Advanced', default_closed=True)
|
||||||
advanced_header.label(text='Advanced')
|
advanced_header.label(text='Advanced')
|
||||||
|
|
||||||
@@ -127,6 +173,7 @@ class ASE_OT_export(Operator, ExportHelper):
|
|||||||
advanced_panel.use_property_split = True
|
advanced_panel.use_property_split = True
|
||||||
advanced_panel.use_property_decorate = False
|
advanced_panel.use_property_decorate = False
|
||||||
advanced_panel.prop(self, 'object_eval_state')
|
advanced_panel.prop(self, 'object_eval_state')
|
||||||
|
advanced_panel.prop(pg, 'should_invert_normals')
|
||||||
|
|
||||||
def invoke(self, context: 'Context', event: 'Event' ) -> Union[Set[str], Set[int]]:
|
def invoke(self, context: 'Context', event: 'Event' ) -> Union[Set[str], Set[int]]:
|
||||||
mesh_objects = [x[0] for x in get_mesh_objects(context.selected_objects)]
|
mesh_objects = [x[0] for x in get_mesh_objects(context.selected_objects)]
|
||||||
@@ -141,16 +188,22 @@ class ASE_OT_export(Operator, ExportHelper):
|
|||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
options = ASEBuilderOptions()
|
|
||||||
options.object_eval_state = self.object_eval_state
|
|
||||||
pg = getattr(context.scene, 'ase_export')
|
pg = getattr(context.scene, 'ase_export')
|
||||||
|
|
||||||
|
options = ASEBuildOptions()
|
||||||
|
options.object_eval_state = self.object_eval_state
|
||||||
|
options.should_export_vertex_colors = pg.should_export_vertex_colors
|
||||||
|
options.vertex_color_mode = pg.vertex_color_mode
|
||||||
|
options.has_vertex_colors = len(get_vertex_color_attributes_from_objects(context.selected_objects)) > 0
|
||||||
|
options.vertex_color_attribute = pg.vertex_color_attribute
|
||||||
options.materials = [x.material for x in pg.material_list]
|
options.materials = [x.material for x in pg.material_list]
|
||||||
|
options.should_invert_normals = pg.should_invert_normals
|
||||||
try:
|
try:
|
||||||
ase = ASEBuilder().build(context, options, context.selected_objects)
|
ase = build_ase(context, options, context.selected_objects)
|
||||||
ASEWriter().write(self.filepath, ase)
|
ASEWriter().write(self.filepath, ase)
|
||||||
self.report({'INFO'}, 'ASE exported successfully')
|
self.report({'INFO'}, 'ASE exported successfully')
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
except ASEBuilderError as e:
|
except ASEBuildError as e:
|
||||||
self.report({'ERROR'}, str(e))
|
self.report({'ERROR'}, str(e))
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
@@ -190,8 +243,9 @@ class ASE_OT_export_collection(Operator, ExportHelper):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
collection = bpy.data.collections.get(self.collection)
|
collection = bpy.data.collections.get(self.collection)
|
||||||
|
|
||||||
options = ASEBuilderOptions()
|
options = ASEBuildOptions()
|
||||||
options.object_eval_state = self.object_eval_state
|
options.object_eval_state = self.object_eval_state
|
||||||
|
options.transform = Matrix.Translation(-Vector(collection.instance_offset))
|
||||||
|
|
||||||
# Iterate over all the objects in the collection.
|
# Iterate over all the objects in the collection.
|
||||||
mesh_objects = get_mesh_objects(collection.all_objects)
|
mesh_objects = get_mesh_objects(collection.all_objects)
|
||||||
@@ -199,8 +253,8 @@ class ASE_OT_export_collection(Operator, ExportHelper):
|
|||||||
options.materials = get_unique_materials([x[0] for x in mesh_objects])
|
options.materials = get_unique_materials([x[0] for x in mesh_objects])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ase = ASEBuilder().build(context, options, collection.all_objects)
|
ase = build_ase(context, options, collection.all_objects)
|
||||||
except ASEBuilderError as e:
|
except ASEBuildError as e:
|
||||||
self.report({'ERROR'}, str(e))
|
self.report({'ERROR'}, str(e))
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
from .ase import ASE
|
||||||
|
|
||||||
|
|
||||||
class ASEFile(object):
|
class ASEFile(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.commands = []
|
self.commands = []
|
||||||
@@ -99,7 +102,7 @@ class ASEWriter(object):
|
|||||||
self.write_command(command)
|
self.write_command(command)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_ase_tree(ase) -> ASEFile:
|
def build_ase_tree(ase: ASE) -> ASEFile:
|
||||||
root = ASEFile()
|
root = ASEFile()
|
||||||
root.add_command('3DSMAX_ASCIIEXPORT').push_datum(200)
|
root.add_command('3DSMAX_ASCIIEXPORT').push_datum(200)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user