11 Commits

Author SHA1 Message Date
Will Snyder
7b9f5c9cfb Prune segments with empty triangle strips. ZE and most versions of ZETools require triangle strips. 2022-10-06 10:23:13 -07:00
William Herald Snyder
84a910f747 When exporting collision primitives, check the swbf_msh_coll_prim primitive property before the name to catch primitives which have incorrect names. Previously the property would only be checked if the primitive was missing a name indicator, but it should be checked first and always because some primitives from XSI have name-type mismatch (cis_hover_aat) 2022-10-05 09:28:27 -04:00
William Herald Snyder
0a1866295c Only include vertex groups used for skinning purposes. More robust requirements for object to be exported as a skin. Misc cleanup and minor refactoring in msh_model_gather. 2022-10-05 09:27:44 -04:00
William Herald Snyder
ec54df21d2 Blend + hardedged transparency emulated via settings + additive emulated by adding the output of the PBSDF and a default Transparent BSDF. Bugfix: animation data is cleared from nodetrees via a stable method. FCurves.clear() is too new... 2022-10-04 09:58:36 -04:00
William Herald Snyder
b2bd9c8316 Blender 2.80-2.83 supports additive blending, so FillMaterialProps will catch that if present 2022-10-04 07:54:25 -04:00
William Herald Snyder
56f6ce6940 Fill doublesidedness and transparency (TODO: figure out additive mapping/emulation) from material settings 2022-10-03 23:06:33 -04:00
William Herald Snyder
f44c7bfdf3 DFS property fill starts from BSDF Principled Base Color input and runs until a texture image is hit. The extension of the texture image's source path is always replaced with .tga. Operator skips previously handled materials. 2022-10-03 22:56:45 -04:00
William Herald Snyder
8bf2196991 Don't change rendertype on matfill and fill texture_0 as well as diffuse map 2022-10-03 21:16:59 -04:00
William Herald Snyder
1252a6d192 Grayscale bumpmaps support via option files 2022-10-03 14:12:21 -04:00
William Herald Snyder
9fcdb3dfb7 Quick and dirty option file parser 2022-10-03 14:11:44 -04:00
William Herald Snyder
dd17fe902e Normal mapping support, though bump mapping is not supported yet. To support it, .tga.option files will have to be read and the various bump mapping munge parameters will need proper interpretation. Emulating the bump effect via shader nodes is straightforward. 2022-10-03 12:30:34 -04:00
5 changed files with 280 additions and 58 deletions

View File

@@ -10,6 +10,8 @@ from math import sqrt
from bpy.props import BoolProperty, EnumProperty, StringProperty from bpy.props import BoolProperty, EnumProperty, StringProperty
from bpy.types import Operator, Menu from bpy.types import Operator, Menu
from .option_file_parser import MungeOptions
import os import os
@@ -33,28 +35,69 @@ class FillSWBFMaterialProperties(bpy.types.Operator):
slots = sum([list(ob.material_slots) for ob in bpy.context.selected_objects if ob.type == 'MESH'],[]) slots = sum([list(ob.material_slots) for ob in bpy.context.selected_objects if ob.type == 'MESH'],[])
mats = [slot.material for slot in slots if (slot.material and slot.material.node_tree)] mats = [slot.material for slot in slots if (slot.material and slot.material.node_tree)]
mats_visited = set()
for mat in mats: for mat in mats:
if mat.name in mats_visited or not mat.swbf_msh_mat:
continue
else:
mats_visited.add(mat.name)
mat.swbf_msh_mat.doublesided = not mat.use_backface_culling
mat.swbf_msh_mat.hardedged_transparency = (mat.blend_method == "CLIP")
mat.swbf_msh_mat.blended_transparency = (mat.blend_method == "BLEND")
mat.swbf_msh_mat.additive_transparency = (mat.blend_method == "ADDITIVE")
# Below is all for filling the diffuse map/texture_0 fields
try: try:
for BSDF_node in [n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED']: for BSDF_node in [n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED']:
base_col = BSDF_node.inputs['Base Color'] base_col = BSDF_node.inputs['Base Color']
for link in base_col.links : stack = []
link_node = link.from_node
if link_node.type != 'TEX_IMAGE': texture_node = None
continue
tex_name = link_node.image.filepath current_socket = base_col
print(tex_name) if base_col.is_linked:
stack.append(base_col.links[0].from_node)
i = tex_name.find(".tga") while stack:
curr_node = stack.pop()
if curr_node.type == 'TEX_IMAGE':
texture_node = curr_node
break
else:
# Crude but good for now
next_nodes = []
for node_input in curr_node.inputs:
for link in node_input.links:
next_nodes.append(link.from_node)
# reversing it so we go from up to down
stack += reversed(next_nodes)
if texture_node is not None:
tex_path = texture_node.image.filepath
tex_name = os.path.basename(tex_path)
i = tex_name.find('.')
# Get rid of trailing number in case one is present # Get rid of trailing number in case one is present
if i > 0: if i > 0:
tex_name = tex_name[0:i+4] tex_name = tex_name[0:i] + ".tga"
refined_tex_path = os.path.join(os.path.dirname(tex_path), tex_name)
mat.swbf_msh_mat.diffuse_map = refined_tex_path
mat.swbf_msh_mat.texture_0 = refined_tex_path
mat.swbf_msh_mat.rendertype = 'NORMAL_BF2'
mat.swbf_msh_mat.diffuse_map = tex_name
break break
except: except:
# Many chances for null ref exceptions. None if user reads doc section... # Many chances for null ref exceptions. None if user reads doc section...
@@ -83,6 +126,7 @@ def draw_matfill_menu(self, context):
# Creates shader nodes to emulate SWBF material properties. # Creates shader nodes to emulate SWBF material properties.
# Will probably only support for a narrow subset of properties... # Will probably only support for a narrow subset of properties...
# So much fun to write this, will probably do all render types by end of October
class GenerateMaterialNodesFromSWBFProperties(bpy.types.Operator): class GenerateMaterialNodesFromSWBFProperties(bpy.types.Operator):
@@ -118,7 +162,9 @@ to provide an exact emulation"""
texture_input_nodes = [] texture_input_nodes = []
surface_output_nodes = [] surface_output_nodes = []
# Op will give up if no diffuse map is present.
# Eventually more nuance will be added for different
# rtypes
diffuse_texture_path = mat_props.diffuse_map diffuse_texture_path = mat_props.diffuse_map
if diffuse_texture_path and os.path.exists(diffuse_texture_path): if diffuse_texture_path and os.path.exists(diffuse_texture_path):
@@ -127,7 +173,6 @@ to provide an exact emulation"""
bsdf = material.node_tree.nodes.new("ShaderNodeBsdfPrincipled") bsdf = material.node_tree.nodes.new("ShaderNodeBsdfPrincipled")
texImage = material.node_tree.nodes.new('ShaderNodeTexImage') texImage = material.node_tree.nodes.new('ShaderNodeTexImage')
texImage.image = bpy.data.images.load(diffuse_texture_path) texImage.image = bpy.data.images.load(diffuse_texture_path)
texImage.image.alpha_mode = 'CHANNEL_PACKED' texImage.image.alpha_mode = 'CHANNEL_PACKED'
@@ -138,16 +183,30 @@ to provide an exact emulation"""
bsdf.inputs["Roughness"].default_value = 1.0 bsdf.inputs["Roughness"].default_value = 1.0
bsdf.inputs["Specular"].default_value = 0.0 bsdf.inputs["Specular"].default_value = 0.0
if mat_props.hardedged_transparency and not mat_props.glow:
material.blend_method = "CLIP"
material.node_tree.links.new(bsdf.inputs['Alpha'], texImage.outputs['Alpha'])
material.use_backface_culling = not bool(mat_props.doublesided) material.use_backface_culling = not bool(mat_props.doublesided)
surface_output_nodes.append(tuple(('BSDF', bsdf))) surface_output_nodes.append(('BSDF', bsdf))
if not mat_props.glow:
if mat_props.hardedged_transparency:
material.blend_method = "CLIP"
material.node_tree.links.new(bsdf.inputs['Alpha'], texImage.outputs['Alpha'])
elif mat_props.blended_transparency:
material.blend_method = "BLEND"
material.node_tree.links.new(bsdf.inputs['Alpha'], texImage.outputs['Alpha'])
elif mat_props.additive_transparency:
if mat_props.glow: # most complex
transparent_bsdf = material.node_tree.nodes.new("ShaderNodeBsdfTransparent")
add_shader = material.node_tree.nodes.new("ShaderNodeAddShader")
material.node_tree.links.new(add_shader.inputs[0], bsdf.outputs["BSDF"])
material.node_tree.links.new(add_shader.inputs[1], transparent_bsdf.outputs["BSDF"])
surface_output_nodes[0] = ('Shader', add_shader)
# Glow (adds another shader output)
else:
emission = material.node_tree.nodes.new("ShaderNodeEmission") emission = material.node_tree.nodes.new("ShaderNodeEmission")
material.node_tree.links.new(emission.inputs['Color'], texImage.outputs['Color']) material.node_tree.links.new(emission.inputs['Color'], texImage.outputs['Color'])
@@ -160,7 +219,7 @@ to provide an exact emulation"""
material.node_tree.links.new(emission.inputs['Strength'], emission_strength_multiplier.outputs[0]) material.node_tree.links.new(emission.inputs['Strength'], emission_strength_multiplier.outputs[0])
surface_output_nodes.append(tuple(("Emission", emission))) surface_output_nodes.append(("Emission", emission))
surfaces_output = None surfaces_output = None
if (len(surface_output_nodes) == 1): if (len(surface_output_nodes) == 1):
@@ -172,14 +231,52 @@ to provide an exact emulation"""
surfaces_output = mix surfaces_output = mix
# Normal/bump mapping (needs more rendertype support!)
if "NORMALMAP" in mat_props.rendertype and mat_props.normal_map and os.path.exists(mat_props.normal_map):
normalMapTexImage = material.node_tree.nodes.new('ShaderNodeTexImage')
normalMapTexImage.image = bpy.data.images.load(mat_props.normal_map)
normalMapTexImage.image.alpha_mode = 'CHANNEL_PACKED'
normalMapTexImage.image.colorspace_settings.name = 'Non-Color'
texture_input_nodes.append(normalMapTexImage)
options = MungeOptions(mat_props.normal_map + ".option")
if options.get_bool("bumpmap"):
# First we must convert the RGB data to brightness
rgb_to_bw_node = material.node_tree.nodes.new("ShaderNodeRGBToBW")
material.node_tree.links.new(rgb_to_bw_node.inputs["Color"], normalMapTexImage.outputs["Color"])
# Now create a bump map node (perhaps we could also use this with normals and just plug color into normal input?)
bumpMapNode = material.node_tree.nodes.new('ShaderNodeBump')
bumpMapNode.inputs["Distance"].default_value = options.get_float("bumpscale", default=1.0)
material.node_tree.links.new(bumpMapNode.inputs["Height"], rgb_to_bw_node.outputs["Val"])
normalsOutputNode = bumpMapNode
else:
normalMapNode = material.node_tree.nodes.new('ShaderNodeNormalMap')
material.node_tree.links.new(normalMapNode.inputs["Color"], normalMapTexImage.outputs["Color"])
normalsOutputNode = normalMapNode
material.node_tree.links.new(bsdf.inputs['Normal'], normalsOutputNode.outputs["Normal"])
output = material.node_tree.nodes.new("ShaderNodeOutputMaterial") output = material.node_tree.nodes.new("ShaderNodeOutputMaterial")
material.node_tree.links.new(output.inputs['Surface'], surfaces_output.outputs[0]) material.node_tree.links.new(output.inputs['Surface'], surfaces_output.outputs[0])
# Scrolling
# This approach works 90% of the time, but notably produces very incorrect results
# on mus1_bldg_world_1,2,3
# Clear all anims in all cases # Clear all anims in all cases
if material.node_tree.animation_data: if material.node_tree.animation_data:
material.node_tree.animation_data.action.fcurves.clear() material.node_tree.animation_data_clear()
if "SCROLL" in mat_props.rendertype: if "SCROLL" in mat_props.rendertype:
@@ -206,7 +303,7 @@ to provide an exact emulation"""
# Don't know how to set interpolation when adding keyframes # Don't know how to set interpolation when adding keyframes
# so we must do it after the fact # so we must do it after the fact
if material.node_tree.animation_data: if material.node_tree.animation_data and material.node_tree.animation_data.action:
for fcurve in material.node_tree.animation_data.action.fcurves: for fcurve in material.node_tree.animation_data.action.fcurves:
for kf in fcurve.keyframe_points.values(): for kf in fcurve.keyframe_points.values():
kf.interpolation = 'LINEAR' kf.interpolation = 'LINEAR'

View File

@@ -32,32 +32,54 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
pure_bones_from_armature = {} pure_bones_from_armature = {}
armature_found = None armature_found = None
objects_to_export = select_objects(export_target) # Non-bone objects that will be exported
blender_objects_to_export = []
for uneval_obj in objects_to_export: # This must be seperate from the list above,
if uneval_obj.type == "ARMATURE": # since exported objects will contain Blender objects as well as bones
# Here we just keep track of all names, regardless of origin
exported_object_names: Set[str] = set()
# Armature must be processed before everything else!
# In this loop we also build a set of names of all objects
# that will be exported. This is necessary so we can prune vertex
# groups that do not reference exported objects in the main
# model building loop below this one.
for uneval_obj in select_objects(export_target):
if uneval_obj.type == "ARMATURE" and not armature_found:
# Keep track of the armature, we don't want to process > 1!
armature_found = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj armature_found = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj
# Get all bones in a separate list. While we iterate through
# objects we removed bones with geometry from this dict. After iteration
# is done, we add the remaining bones to the models from exported
# scene objects.
pure_bones_from_armature = expand_armature(armature_found) pure_bones_from_armature = expand_armature(armature_found)
break # All bones to set
exported_object_names.update(pure_bones_from_armature.keys())
elif not (uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents):
exported_object_names.add(uneval_obj.name)
blender_objects_to_export.append(uneval_obj)
else:
pass
for uneval_obj in objects_to_export: for uneval_obj in blender_objects_to_export:
if uneval_obj.type == "ARMATURE" or (uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents):
continue
obj = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj obj = uneval_obj.evaluated_get(depsgraph) if apply_modifiers else uneval_obj
check_for_bad_lod_suffix(obj) check_for_bad_lod_suffix(obj)
# Test for a mesh object that is actually a BONE (shares name with bone_parent) # Test for a mesh object that should be a BONE on export.
# If so, we inject geometry into the BONE while not modifying it's transform/name # If so, we inject geometry into the BONE while not modifying it's transform/name
if obj.parent_bone and obj.parent_bone in pure_bones_from_armature: # and remove it from the set of BONES without geometry (pure).
model = pure_bones_from_armature[obj.parent_bone] if obj.name in pure_bones_from_armature:
# Since we found a composite bone, removed it from the dict of pure bones model = pure_bones_from_armature.pop(obj.name)
pure_bones_from_armature.pop(obj.parent_bone)
else: else:
model = Model() model = Model()
model.name = obj.name model.name = obj.name
model.model_type = get_model_type(obj, skeleton_only) model.model_type = ModelType.NULL if skeleton_only else get_model_type(obj, armature_found)
model.hidden = get_is_model_hidden(obj) model.hidden = get_is_model_hidden(obj)
transform = obj.matrix_local transform = obj.matrix_local
@@ -82,10 +104,19 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
model.transform.rotation = convert_rotation_space(local_rotation) model.transform.rotation = convert_rotation_space(local_rotation)
model.transform.translation = convert_vector_space(local_translation) model.transform.translation = convert_vector_space(local_translation)
if obj.type in MESH_OBJECT_TYPES: if obj.type in MESH_OBJECT_TYPES and not skeleton_only:
# Vertex groups are often used for purposes other than skinning.
# Here we gather all vgroups and select the ones that reference
# objects included in the export.
valid_vgroup_indices : Set[int] = set()
if model.model_type == ModelType.SKIN:
valid_vgroups = [group for group in obj.vertex_groups if group.name in exported_object_names]
valid_vgroup_indices = { group.index for group in valid_vgroups }
model.bone_map = [ group.name for group in valid_vgroups ]
mesh = obj.to_mesh() mesh = obj.to_mesh()
model.geometry = create_mesh_geometry(mesh, obj.vertex_groups) model.geometry = create_mesh_geometry(mesh, valid_vgroup_indices)
obj.to_mesh_clear() obj.to_mesh_clear()
@@ -98,9 +129,6 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has " raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has "
f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up " f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up "
f"and try again!") f"and try again!")
if obj.vertex_groups:
model.bone_map = [group.name for group in obj.vertex_groups]
if get_is_collision_primitive(obj): if get_is_collision_primitive(obj):
model.collisionprimitive = get_collision_primitive(obj) model.collisionprimitive = get_collision_primitive(obj)
@@ -109,9 +137,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
# We removed all composite bones after looking through the objects, # We removed all composite bones after looking through the objects,
# so the bones left are all pure and we add them all here. # so the bones left are all pure and we add them all here.
models_list += pure_bones_from_armature.values() return (models_list + list(pure_bones_from_armature.values()), armature_found)
return (models_list, armature_found)
@@ -127,7 +153,7 @@ def create_parents_set() -> Set[str]:
return parents return parents
def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[GeometrySegment]: def create_mesh_geometry(mesh: bpy.types.Mesh, valid_vgroup_indices: Set[int]) -> List[GeometrySegment]:
""" Creates a list of GeometrySegment objects from a Blender mesh. """ Creates a list of GeometrySegment objects from a Blender mesh.
Does NOT create triangle strips in the GeometrySegment however. """ Does NOT create triangle strips in the GeometrySegment however. """
@@ -148,7 +174,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
for segment in segments: for segment in segments:
segment.colors = [] segment.colors = []
if has_weights: if valid_vgroup_indices:
for segment in segments: for segment in segments:
segment.weights = [] segment.weights = []
@@ -185,8 +211,9 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
if segment.weights is not None: if segment.weights is not None:
for v in mesh.vertices[vertex_index].groups: for v in mesh.vertices[vertex_index].groups:
yield v.group if v.group in valid_vgroup_indices:
yield v.weight yield v.group
yield v.weight
vertex_cache_entry = tuple(get_cache_vertex()) vertex_cache_entry = tuple(get_cache_vertex())
cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index) cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index)
@@ -213,8 +240,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
if segment.weights is not None: if segment.weights is not None:
groups = mesh.vertices[vertex_index].groups groups = mesh.vertices[vertex_index].groups
segment.weights.append([VertexWeight(v.weight, v.group) for v in groups if v.group in valid_vgroup_indices])
segment.weights.append([VertexWeight(v.weight, v.group) for v in groups])
return new_index return new_index
@@ -233,12 +259,29 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
return segments return segments
def get_model_type(obj: bpy.types.Object, skel_only: bool) -> ModelType: def get_model_type(obj: bpy.types.Object, armature_found: bpy.types.Object) -> ModelType:
""" Get the ModelType for a Blender object. """ """ Get the ModelType for a Blender object. """
if obj.type in MESH_OBJECT_TYPES and not skel_only: if obj.type in MESH_OBJECT_TYPES:
if obj.vertex_groups: # Objects can have vgroups for non-skinning purposes.
return ModelType.SKIN # If we can find one vgroup that shares a name with a bone in the
# armature, we know the vgroup is for weighting purposes and thus
# the object is a skin. Otherwise, interpret it as a static mesh.
# We must also check that an armature included in the export
# and that it is the same one this potential skin is weighting to.
# If we failed to do this, a user could export a selected object
# that is a skin, but the weight data in the export would reference
# nonexistent models!
if (obj.vertex_groups and armature_found and
obj.parent and obj.parent.name == armature_found.name):
for vgroup in obj.vertex_groups:
if vgroup.name in armature_found.data.bones:
return ModelType.SKIN
return ModelType.STATIC
else: else:
return ModelType.STATIC return ModelType.STATIC
@@ -311,6 +354,14 @@ def get_collision_primitive_shape(obj: bpy.types.Object) -> CollisionPrimitiveSh
""" Gets the CollisionPrimitiveShape of an object or raises an error if """ Gets the CollisionPrimitiveShape of an object or raises an error if
it can't. """ it can't. """
# arc170 fighter has examples of box colliders without proper naming
# and cis_hover_aat has a cylinder which is named p_vehiclesphere.
# To export these properly we must check the collision_prim property
# that was assigned on import BEFORE looking at the name.
prim_type = obj.swbf_msh_coll_prim.prim_type
if prim_type in [item.value for item in CollisionPrimitiveShape]:
return CollisionPrimitiveShape(prim_type)
name = obj.name.lower() name = obj.name.lower()
if "sphere" in name or "sphr" in name or "spr" in name: if "sphere" in name or "sphr" in name or "spr" in name:
@@ -320,11 +371,6 @@ def get_collision_primitive_shape(obj: bpy.types.Object) -> CollisionPrimitiveSh
if "box" in name or "cube" in name or "cuboid" in name: if "box" in name or "cube" in name or "cuboid" in name:
return CollisionPrimitiveShape.BOX return CollisionPrimitiveShape.BOX
# arc170 fighter has examples of box colliders without proper naming
prim_type = obj.swbf_msh_coll_prim.prim_type
if prim_type in [item.value for item in CollisionPrimitiveShape]:
return CollisionPrimitiveShape(prim_type)
raise RuntimeError(f"Object '{obj.name}' has no primitive type specified in it's name!") raise RuntimeError(f"Object '{obj.name}' has no primitive type specified in it's name!")

View File

@@ -8,6 +8,25 @@ import math
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
# Convert model with geometry to null.
# Currently not used, but could be necessary in the future.
def make_null(model : Model):
model.model_type = ModelType.NULL
bone_map = None
geometry = None
# I think this is all we need to check for to avoid
# common ZE/ZETools crashes...
def validate_geometry_segment(segment : GeometrySegment) -> bool:
if not segment.positions or not segment.triangle_strips:
return False
else:
return True
def inject_dummy_data(model : Model): def inject_dummy_data(model : Model):
""" Adds a triangle and material to the model (scene root). Needed to export zenasst-compatible skeletons. """ """ Adds a triangle and material to the model (scene root). Needed to export zenasst-compatible skeletons. """
model.hidden = True model.hidden = True

View File

@@ -9,7 +9,7 @@ from mathutils import Vector
from .msh_model import Model, Animation, ModelType from .msh_model import Model, Animation, ModelType
from .msh_scene import Scene, SceneAABB from .msh_scene import Scene, SceneAABB
from .msh_model_gather import gather_models from .msh_model_gather import gather_models
from .msh_model_utilities import sort_by_parent, has_multiple_root_models, reparent_model_roots, get_model_world_matrix, inject_dummy_data from .msh_model_utilities import make_null, validate_geometry_segment, sort_by_parent, has_multiple_root_models, reparent_model_roots, get_model_world_matrix, inject_dummy_data
from .msh_model_triangle_strips import create_models_triangle_strips from .msh_model_triangle_strips import create_models_triangle_strips
from .msh_material import * from .msh_material import *
from .msh_material_gather import gather_materials from .msh_material_gather import gather_materials
@@ -53,6 +53,20 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t
for segment in model.geometry: for segment in model.geometry:
segment.triangle_strips = segment.triangles segment.triangle_strips = segment.triangles
# After generating triangle strips we must prune any segments that don't have
# them, or else ZE and most versions of ZETools will crash.
# We could also make models with no valid segments nulls, since they might as well be,
# but that could have unforseeable consequences further down the modding pipeline
# and is not necessary to avoid the aforementioned crashes...
for model in scene.models:
if model.geometry is not None:
# Doing this in msh_model_gather would be messy and the presence/absence
# of triangle strips is required for a validity check.
model.geometry = [segment for segment in model.geometry if validate_geometry_segment(segment)]
#if not model.geometry:
# make_null(model)
if has_multiple_root_models(scene.models): if has_multiple_root_models(scene.models):
scene.models = reparent_model_roots(scene.models) scene.models = reparent_model_roots(scene.models)

View File

@@ -0,0 +1,46 @@
""" Parses .tga.option and .msh.option files. Only used with the former as of now. """
import os
class MungeOptions:
def __init__(self, path_to_option_file):
self.options = {}
if os.path.exists(path_to_option_file):
with open(path_to_option_file, 'r') as option_file:
option_text = option_file.read()
option_parts = option_text.split()
current_parameter = ""
for part in option_parts:
if part.startswith("-"):
current_parameter = part[1:]
self.options[current_parameter] = ""
elif current_parameter:
current_value = self.options[current_parameter]
# Keep adding to value in case there are vector options
self.options[current_parameter] += part if not current_value else (" " + part)
def is_option_present(self, param):
return param in self.options
def get_bool(self, param, default=False):
return True if param in self.options else default
def get_float(self, param, default=0.0):
if param in self.options:
try:
result = float(self.options[param])
except:
result = default
finally:
return result
else:
return default
def get_string(self, param, default=""):
return self.options.get(param, default)