From 897410e96ca692f62443e368ea5e09f733565df7 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 16 Aug 2019 06:13:30 +0200 Subject: [PATCH 01/35] Add shape creation functions. --- scene/__init__.py | 0 scene/shapes.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 scene/__init__.py create mode 100644 scene/shapes.py diff --git a/scene/__init__.py b/scene/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scene/shapes.py b/scene/shapes.py new file mode 100644 index 0000000..2f396a9 --- /dev/null +++ b/scene/shapes.py @@ -0,0 +1,45 @@ +import bpy +import bmesh + +def create_sphere(name="new_sphere", size=1): + ''' + Creates a sphere with the given size. + ''' + scene = bpy.context.collection + + mesh = bpy.data.meshes.new(name) + sphere = bpy.data.objects.new(name, mesh) + + scene.objects.link(sphere) + + bm = bmesh.new() + bmesh.ops.create_uvsphere(bm, + u_segments=16, v_segments=16, + diameter=size + ) + bm.to_mesh(mesh) + bm.free() + + return sphere + +def create_node_object(name="new_cone", base_size=1, height=3): + ''' + Creates a cone with the given sizes. + ''' + scene = bpy.context.collection + + mesh = bpy.data.meshes.new(name) + cone = bpy.data.objects.new(name, mesh) + + scene.objects.link(cone) + + bm = bmesh.new() + bmesh.ops.create_cone(bm, + segments=16, + diameter1=base_size, diameter2=base_size, + depth=height + ) + bm.to_mesh(mesh) + bm.free() + + return cone From df17d7298e41ecb107627f5e9b04c5972c344050 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 16 Aug 2019 06:15:48 +0200 Subject: [PATCH 02/35] Fix a function name. --- scene/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scene/shapes.py b/scene/shapes.py index 2f396a9..89fd412 100644 --- a/scene/shapes.py +++ b/scene/shapes.py @@ -22,7 +22,7 @@ def create_sphere(name="new_sphere", size=1): return sphere -def create_node_object(name="new_cone", base_size=1, height=3): +def create_cone(name="new_cone", base_size=1, height=3): ''' Creates a cone with the given sizes. ''' From 0b8ee7d20fa0700de5a28ea1295f02fc03994cd6 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 16 Aug 2019 06:47:41 +0200 Subject: [PATCH 03/35] Stubbed out some Halo <-> scene interaction classes. --- halo1/types.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 halo1/types.py diff --git a/halo1/types.py b/halo1/types.py new file mode 100644 index 0000000..388e170 --- /dev/null +++ b/halo1/types.py @@ -0,0 +1,63 @@ +class HBase: + ''' + The base of all the translation classes that + handle conversion between Halo and Blender. + ''' + id_keys = { + "long": 'halo_object', + "short": 'h', + } + + +class HRegion(HBase): + ''' + A section of a model that can be different + depending on what permutation is selected. + ''' + id_keys = { + "long": 'region', + "short": 'r', + } + + +class HPermutation(HRegion): + ''' + One version of a region. + ''' + id_keys = { + "long": 'permutation', + "short": 'p', + } + + +class HNode(HBase): + ''' + An object that allows a mesh to be deformed, + and markers to be moved around. + ''' + id_keys = { + "long": 'node', + "short": 'n', + } + + +class HMarker(HNode): + ''' + A point that can be the origin of an effect + or the point of interaction. + ''' + id_keys = { + "long": 'marker', + "short": 'm', + } + + +class HMesh(HBase): + ''' + A set of surfaces that dictates how + its part of the model looks. + ''' + id_keys = { + "long": 'mesh', + "short": '', + } From 528e555d182467316cdf04e8b1740a6aa91ad2ed Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 18 Aug 2019 04:21:27 +0200 Subject: [PATCH 04/35] Simple in theory. Complicated in practice. Delete halo1/types --- halo1/types.py | 63 -------------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 halo1/types.py diff --git a/halo1/types.py b/halo1/types.py deleted file mode 100644 index 388e170..0000000 --- a/halo1/types.py +++ /dev/null @@ -1,63 +0,0 @@ -class HBase: - ''' - The base of all the translation classes that - handle conversion between Halo and Blender. - ''' - id_keys = { - "long": 'halo_object', - "short": 'h', - } - - -class HRegion(HBase): - ''' - A section of a model that can be different - depending on what permutation is selected. - ''' - id_keys = { - "long": 'region', - "short": 'r', - } - - -class HPermutation(HRegion): - ''' - One version of a region. - ''' - id_keys = { - "long": 'permutation', - "short": 'p', - } - - -class HNode(HBase): - ''' - An object that allows a mesh to be deformed, - and markers to be moved around. - ''' - id_keys = { - "long": 'node', - "short": 'n', - } - - -class HMarker(HNode): - ''' - A point that can be the origin of an effect - or the point of interaction. - ''' - id_keys = { - "long": 'marker', - "short": 'm', - } - - -class HMesh(HBase): - ''' - A set of surfaces that dictates how - its part of the model looks. - ''' - id_keys = { - "long": 'mesh', - "short": '', - } From 6bc3358701f9176d3628bbb77b9f74bc75a9d713 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 18 Aug 2019 04:29:50 +0200 Subject: [PATCH 05/35] Floats, not ints. --- scene/shapes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scene/shapes.py b/scene/shapes.py index 89fd412..447ab10 100644 --- a/scene/shapes.py +++ b/scene/shapes.py @@ -1,7 +1,7 @@ import bpy import bmesh -def create_sphere(name="new_sphere", size=1): +def create_sphere(name="new_sphere", size=1.0): ''' Creates a sphere with the given size. ''' @@ -22,7 +22,7 @@ def create_sphere(name="new_sphere", size=1): return sphere -def create_cone(name="new_cone", base_size=1, height=3): +def create_cone(name="new_cone", base_size=1.0, height=3.0): ''' Creates a cone with the given sizes. ''' From c95d85869215e988c48389587b49928b3f1e74e9 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Mon, 19 Aug 2019 09:23:32 +0200 Subject: [PATCH 06/35] Updated whitespace. --- scene/shapes.py | 70 ++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/scene/shapes.py b/scene/shapes.py index 447ab10..1dc80d2 100644 --- a/scene/shapes.py +++ b/scene/shapes.py @@ -2,44 +2,44 @@ import bmesh def create_sphere(name="new_sphere", size=1.0): - ''' - Creates a sphere with the given size. - ''' - scene = bpy.context.collection + ''' + Creates a sphere with the given size. + ''' + scene = bpy.context.collection - mesh = bpy.data.meshes.new(name) - sphere = bpy.data.objects.new(name, mesh) + mesh = bpy.data.meshes.new(name) + sphere = bpy.data.objects.new(name, mesh) - scene.objects.link(sphere) + scene.objects.link(sphere) - bm = bmesh.new() - bmesh.ops.create_uvsphere(bm, - u_segments=16, v_segments=16, - diameter=size - ) - bm.to_mesh(mesh) - bm.free() + bm = bmesh.new() + bmesh.ops.create_uvsphere(bm, + u_segments=16, v_segments=16, + diameter=size + ) + bm.to_mesh(mesh) + bm.free() - return sphere + return sphere def create_cone(name="new_cone", base_size=1.0, height=3.0): - ''' - Creates a cone with the given sizes. - ''' - scene = bpy.context.collection - - mesh = bpy.data.meshes.new(name) - cone = bpy.data.objects.new(name, mesh) - - scene.objects.link(cone) - - bm = bmesh.new() - bmesh.ops.create_cone(bm, - segments=16, - diameter1=base_size, diameter2=base_size, - depth=height - ) - bm.to_mesh(mesh) - bm.free() - - return cone + ''' + Creates a cone with the given sizes. + ''' + scene = bpy.context.collection + + mesh = bpy.data.meshes.new(name) + cone = bpy.data.objects.new(name, mesh) + + scene.objects.link(cone) + + bm = bmesh.new() + bmesh.ops.create_cone(bm, + segments=16, + diameter1=base_size, diameter2=base_size, + depth=height + ) + bm.to_mesh(mesh) + bm.free() + + return cone From 6deb743bd16e101c7918ede4d154637f4250cd59 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Thu, 29 Aug 2019 12:42:22 +0200 Subject: [PATCH 07/35] Fix the weird module importing stuff. --- __init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 1de81d9..98517eb 100644 --- a/__init__.py +++ b/__init__.py @@ -10,7 +10,12 @@ "category": "Import-Export" } -from .menu import topbar_dropdown +# Make the BlendKrieg folder recognized as a module +# so that we can import things more easily. +import sys +import os +sys.path.insert(0, os.path.dirname(__file__)) + def register(): ''' From e2502f6c808893c378563604cda71416fec01b36 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Thu, 29 Aug 2019 12:44:12 +0200 Subject: [PATCH 08/35] Set up all UI elements for simple node importing. --- __init__.py | 5 ++ menu/import_export/__init__.py | 0 menu/import_export/halo1_model.py | 98 +++++++++++++++++++++++++++++++ menu/topbar_dropdown.py | 7 +++ 4 files changed, 110 insertions(+) create mode 100644 menu/import_export/__init__.py create mode 100644 menu/import_export/halo1_model.py diff --git a/__init__.py b/__init__.py index 98517eb..b8d1433 100644 --- a/__init__.py +++ b/__init__.py @@ -16,12 +16,16 @@ import os sys.path.insert(0, os.path.dirname(__file__)) +# Import all submodules. +from menu import topbar_dropdown +from menu.import_export import halo1_model def register(): ''' Registers classes on load by calling the register functions in their respective modules. ''' + halo1_model.register() topbar_dropdown.register() def unregister(): @@ -30,6 +34,7 @@ def unregister(): unregister functions in their respective modules. ''' topbar_dropdown.unregister() + halo1_model.register() if __name__ == "__main__": register() diff --git a/menu/import_export/__init__.py b/menu/import_export/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py new file mode 100644 index 0000000..a64bf64 --- /dev/null +++ b/menu/import_export/halo1_model.py @@ -0,0 +1,98 @@ +import bpy +from bpy.utils import register_class, unregister_class +from bpy.props import BoolProperty, FloatProperty, StringProperty, EnumProperty +from bpy_extras.io_utils import ImportHelper, ExportHelper, orientation_helper, path_reference_mode, axis_conversion + +#@orientation_helper(axis_forward='-Z') Find the right value for this. +class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): + """Load a Gbxmodel or jms file.""" + bl_idname = "import_scene.halo1_model" + bl_label = "Import Halo 1 Model" + bl_options = {'PRESET', 'UNDO'} + + # Import-file-dialog settings: + + filename_ext = ".gbxmodel" + filter_glob: StringProperty( + default="*.gbxmodel;*.model;*.jms", + options={'HIDDEN'}, + ) + + # Node settings: + + use_nodes: BoolProperty( + name="Import Nodes", + description="Import the nodes/frames.", + default=True, + ) + use_armatures: BoolProperty( + name="Import Nodes as Armature", + description="Import the nodes as armature.", + default=False, + ) + + # Scaling settings: + + scale_enum: EnumProperty( + name="Scale", + items=( + ('METRIC', "Blender", "Use Blender's metric scaling."), + ('MAX', "3ds Max", "Use 3dsmax's 100xHalo scale."), + ('HALO', "Internal", "Use Halo's internal 1.0 scale (small)."), + ('CUSTOM', "Custom", "Set your own scaling."), + ) + ) + scale_float: FloatProperty( + name="Custom Scale", + description="Set your own scale.", + default=1.0, + ) + + def execute(self, context): + print("Use nodes: " + str(self.use_nodes)) + print("Use armatures: " + str(self.use_armatures)) + print("Scale Enum: " + str(self.scale_enum)) + print("Scale Float: " + str(self.scale_float)) + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + + # Node settings elements: + + box = layout.box() + row = box.row() + row.label(text="Nodes:") + row.prop(self, "use_nodes") + if self.use_nodes: + box.prop(self, "use_armatures") + + # Scale settings elements: + + box = layout.box() + box.label(text="Scale:") + row = box.row() + row.prop(self, "scale_enum", expand=True) + + if self.scale_enum == 'CUSTOM': + row = box.row() + row.prop(self, "scale_float") + + +# Enumerate all classes for easy register/unregister. +classes = ( + MT_krieg_ImportHalo1Model, +) + +def register(): + for cls in classes: + register_class(cls) + + +def unregister(): + for cls in reversed(classes): #Reversed because: first in, last out. + unregister_class(cls) + + +if __name__ == "__main__": + register() diff --git a/menu/topbar_dropdown.py b/menu/topbar_dropdown.py index 0fd30d5..614acb2 100644 --- a/menu/topbar_dropdown.py +++ b/menu/topbar_dropdown.py @@ -1,6 +1,8 @@ from bpy.utils import register_class, unregister_class from bpy.types import Menu, TOPBAR_MT_editor_menus +from menu.import_export import halo1_model + class TOPBAR_MT_krieg(Menu): bl_idname = "TOPBAR_MT_krieg_ext" bl_label = "Krieg" @@ -29,6 +31,11 @@ def draw(self, context): # Halo 1: + layout.operator( + halo1_model.MT_krieg_ImportHalo1Model.bl_idname, + text="Halo 1 Model (.gbxmodel, .model, .jms)" + ) + layout.separator() # Whatever else: From 3e0d121d6304697ff6d11c76f61f2f99c7e8d86a Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Thu, 29 Aug 2019 23:51:05 +0200 Subject: [PATCH 09/35] Undo import thing --- __init__.py | 10 ++-------- menu/topbar_dropdown.py | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/__init__.py b/__init__.py index b8d1433..2236f89 100644 --- a/__init__.py +++ b/__init__.py @@ -10,15 +10,9 @@ "category": "Import-Export" } -# Make the BlendKrieg folder recognized as a module -# so that we can import things more easily. -import sys -import os -sys.path.insert(0, os.path.dirname(__file__)) - # Import all submodules. -from menu import topbar_dropdown -from menu.import_export import halo1_model +from .menu import topbar_dropdown +from .menu.import_export import halo1_model def register(): ''' diff --git a/menu/topbar_dropdown.py b/menu/topbar_dropdown.py index 614acb2..da9c06c 100644 --- a/menu/topbar_dropdown.py +++ b/menu/topbar_dropdown.py @@ -1,7 +1,7 @@ from bpy.utils import register_class, unregister_class from bpy.types import Menu, TOPBAR_MT_editor_menus -from menu.import_export import halo1_model +from .import_export import halo1_model class TOPBAR_MT_krieg(Menu): bl_idname = "TOPBAR_MT_krieg_ext" From 657cf2be23400947126562f4e47ad836d5f31813 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Thu, 29 Aug 2019 23:51:21 +0200 Subject: [PATCH 10/35] Update a class docstring. --- menu/import_export/halo1_model.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index a64bf64..c599fbb 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -5,7 +5,11 @@ #@orientation_helper(axis_forward='-Z') Find the right value for this. class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): - """Load a Gbxmodel or jms file.""" + """ + The import operator for gbxmodel/jms models. + This stores the properties when inside of the import dialog, and + it specifies what to show on the side panel when importing. + """ bl_idname = "import_scene.halo1_model" bl_label = "Import Halo 1 Model" bl_options = {'PRESET', 'UNDO'} From 33fdd1da2661d167460b28d3d03d7ad843da2506 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 30 Aug 2019 11:58:00 +0200 Subject: [PATCH 11/35] Add untested halo1 model reading function. --- halo1/__init__.py | 0 halo1/model.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 halo1/__init__.py create mode 100644 halo1/model.py diff --git a/halo1/__init__.py b/halo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/halo1/model.py b/halo1/model.py new file mode 100644 index 0000000..0e4c946 --- /dev/null +++ b/halo1/model.py @@ -0,0 +1,41 @@ +import bpy + +from reclaimer.hek.defs.mode import mode_def +from reclaimer.hek.defs.mod2 import mod2_def +from reclaimer.model.jms import read_jms +from reclaimer.model.model_decompilation import extract_model +from mozzarilla.windows.tag_converters.model_converter import convert_model + +def read_halo1model(filepath): + '''Takes a halo1 model file and turns it into a jms class.''' + + if filepath.endswith('.gbxmodel'): + # Load Gbxmodel + mod2 = mod2_def.build(filepath=filepath) + #TODO: Get all lod permutations. + jms = extract_model(mod2)[0] #Only getting the superhigh perms for now + + return jms + + if filepath.endswith('.model'): + # Load Xbox model + mode = mode_def.build(filepath=filepath) + # Convert the Xbox model to a Gbxmodel + mod2 = mod2_def.build() + convert_model(mod2, mode, True) #True means: convert to mod2 + #TODO: Get all lod permutations. + jms = extract_model(mod2)[0] #Only getting the superhigh perms for now + + return jms + + if filepath.endswith('.jms'): + # Read jms file into string. + jms_string = "" + with open(filepath, 'r') as jms_file: + jms_string = jms_file.read() + # Read Jms data from string. + jms = read_jms(jms_string) + # Make sure it's a Halo 1 jms + assert(jms.version == "8200") + + return jms From 7c2166830ce16285fcae4f43c550ec1588c54636 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 30 Aug 2019 12:46:22 +0200 Subject: [PATCH 12/35] Fixed import_halo1model to actually work --- halo1/model.py | 133 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 9 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index 0e4c946..a1ad98c 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -1,20 +1,25 @@ import bpy -from reclaimer.hek.defs.mode import mode_def -from reclaimer.hek.defs.mod2 import mod2_def -from reclaimer.model.jms import read_jms -from reclaimer.model.model_decompilation import extract_model -from mozzarilla.windows.tag_converters.model_converter import convert_model +import os + +# TODO: Figure out why I can't import these without ..lib. +from ..lib.reclaimer.hek.defs.mode import mode_def +from ..lib.reclaimer.hek.defs.mod2 import mod2_def +from ..lib.reclaimer.model.jms import read_jms +from ..lib.reclaimer.model.model_decompilation import extract_model def read_halo1model(filepath): '''Takes a halo1 model file and turns it into a jms class.''' + # Workaround for reclaimer's brokenness on Linux + filepath = os.path.realpath(filepath) + if filepath.endswith('.gbxmodel'): # Load Gbxmodel mod2 = mod2_def.build(filepath=filepath) #TODO: Get all lod permutations. - jms = extract_model(mod2)[0] #Only getting the superhigh perms for now - + #Only getting the superhigh perms for now + jms = extract_model(mod2.data.tagdata, write_jms=False)[0] return jms if filepath.endswith('.model'): @@ -24,7 +29,8 @@ def read_halo1model(filepath): mod2 = mod2_def.build() convert_model(mod2, mode, True) #True means: convert to mod2 #TODO: Get all lod permutations. - jms = extract_model(mod2)[0] #Only getting the superhigh perms for now + #Only getting the superhigh perms for now + jms = extract_model(mod2.data.tagdata, write_jms=False)[0] return jms @@ -34,8 +40,117 @@ def read_halo1model(filepath): with open(filepath, 'r') as jms_file: jms_string = jms_file.read() # Read Jms data from string. - jms = read_jms(jms_string) + jms = read_jms(jms_string, write_jms=False) # Make sure it's a Halo 1 jms assert(jms.version == "8200") return jms + + +# The next is copied from mozzarilla.windows.tag_converters.model_converter +# Too many references to libraries that aren't even used by the function +# that we're importing. +# It relied on threadsafe_tkinter, which relies on tkinter, +# which relies on who knows what. + +# Just ignore this whole part for now + +from reclaimer.hek.defs.mod2 import fast_mod2_def +from reclaimer.stubbs.defs.mode import fast_mode_def + +def convert_model(src_tag, dst_tag, to_gbxmodel): + src_tag_data = src_tag.data.tagdata + dst_tag_data = dst_tag.data.tagdata + + # move the first 14 header fields from src tag into dst tag + # (except for the flags since usually ZONER shouldnt be copied) + dst_tag_data[1: 14] = src_tag_data[1: 14] + for flag_name in src_tag_data.flags.NAME_MAP: + if hasattr(dst_tag_data.flags, flag_name): + dst_tag_data.flags[flag_name] = src_tag_data.flags[flag_name] + + # fix the fact the mode and mod2 store stuff related to lods + # in reverse on most platforms(pc stubbs is an exception) + if dst_tag_data.superhigh_lod_cutoff < dst_tag_data.superlow_lod_cutoff: + tmp0 = dst_tag_data.superhigh_lod_cutoff + tmp1 = dst_tag_data.high_lod_cutoff + dst_tag_data.superhigh_lod_cutoff = dst_tag_data.superlow_lod_cutoff + dst_tag_data.high_lod_cutoff = dst_tag_data.low_lod_cutoff + dst_tag_data.low_lod_cutoff = tmp1 + dst_tag_data.superlow_lod_cutoff = tmp0 + + tmp0 = dst_tag_data.superhigh_lod_nodes + tmp1 = dst_tag_data.high_lod_nodes + dst_tag_data.superhigh_lod_nodes = dst_tag_data.superlow_lod_nodes + dst_tag_data.high_lod_nodes = dst_tag_data.low_lod_nodes + dst_tag_data.low_lod_nodes = tmp1 + dst_tag_data.superlow_lod_nodes = tmp0 + + # make all markers global ones + if hasattr(src_tag, "globalize_local_markers"): + src_tag.globalize_local_markers() + + # move the markers, nodes, regions, and shaders, from mode into mod2 + dst_tag_data.markers = src_tag_data.markers + dst_tag_data.nodes = src_tag_data.nodes + dst_tag_data.regions = src_tag_data.regions + dst_tag_data.shaders = src_tag_data.shaders + + # give the mod2 as many geometries as the mode + src_tag_geoms = src_tag_data.geometries.STEPTREE + dst_tag_geoms = dst_tag_data.geometries.STEPTREE + dst_tag_geoms.extend(len(src_tag_geoms)) + + # copy the data from the src_tag_geoms into the dst_tag_geoms + for i in range(len(dst_tag_geoms)): + # give the dst_tag_geom as many parts as the src_tag_geom + src_tag_parts = src_tag_geoms[i].parts.STEPTREE + dst_tag_parts = dst_tag_geoms[i].parts.STEPTREE + dst_tag_parts.extend(len(src_tag_parts)) + + # copy the data from the src_tag_parts into the dst_tag_parts + for j in range(len(dst_tag_parts)): + src_tag_part = src_tag_parts[j] + dst_tag_part = dst_tag_parts[j] + + # move the first 9 part fields from src_tag into dst_tag + # (except for the flags since usually ZONER shouldnt be copied) + dst_tag_part[1: 9] = src_tag_part[1: 9] + + src_local_nodes = getattr(src_tag_part, "local_nodes", None) + dst_local_nodes = getattr(dst_tag_part, "local_nodes", None) + if not getattr(src_tag_part.flags, "ZONER", False): + src_local_nodes = None + + if dst_local_nodes and src_local_nodes: + # converting from a gbxmodel with local nodes to a gbxmodel + # with local nodes. copy the local nodes and node count + dst_tag_part.flags.ZONER = True + dst_tag_part.local_node_count = src_tag_part.local_node_count + dst_tag_part.local_nodes[:] = src_local_nodes[:] + elif src_local_nodes: + # converting from a gbxmodel with local nodes to + # something without them. make the nodes absolute. + src_tag.delocalize_part_nodes(i, j) + + # move the vertices and triangles from the src_tag into the dst_tag + dst_tag_part.triangles = src_tag_part.triangles + dst_tag_part.uncompressed_vertices = src_tag_part.uncompressed_vertices + dst_tag_part.compressed_vertices = src_tag_part.compressed_vertices + + uncomp_verts = dst_tag_part.uncompressed_vertices + comp_verts = dst_tag_part.compressed_vertices + + if to_gbxmodel: + # if the compressed vertices are valid or + # the uncompressed are not then we don't have + # any conversion to do(already uncompressed) + if not uncomp_verts.size or comp_verts.size: + dst_tag.decompress_part_verts(i, j) + elif not comp_verts.size or uncomp_verts.size: + # the uncompressed vertices are valid or + # the compressed are not, so we don't have + # any conversion to do(already compressed) + dst_tag.compress_part_verts(i, j) + + dst_tag.calc_internal_data() From 2d362cc8d5032006224da4e8b0eed10528ed05c4 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 30 Aug 2019 12:50:39 +0200 Subject: [PATCH 13/35] Fix jms import. --- halo1/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo1/model.py b/halo1/model.py index a1ad98c..10d25e1 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -40,7 +40,7 @@ def read_halo1model(filepath): with open(filepath, 'r') as jms_file: jms_string = jms_file.read() # Read Jms data from string. - jms = read_jms(jms_string, write_jms=False) + jms = read_jms(jms_string) # Make sure it's a Halo 1 jms assert(jms.version == "8200") From c1dde588c0f7caec46850cc1346578c429bcb0e8 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 30 Aug 2019 13:20:04 +0200 Subject: [PATCH 14/35] Import nodes --- halo1/model.py | 29 +++++++++++++++++++++++++++++ menu/import_export/halo1_model.py | 9 +++++++++ 2 files changed, 38 insertions(+) diff --git a/halo1/model.py b/halo1/model.py index 10d25e1..c120076 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -8,6 +8,8 @@ from ..lib.reclaimer.model.jms import read_jms from ..lib.reclaimer.model.model_decompilation import extract_model +from ..scene.shapes import create_sphere + def read_halo1model(filepath): '''Takes a halo1 model file and turns it into a jms class.''' @@ -46,6 +48,33 @@ def read_halo1model(filepath): return jms +def import_halo1_nodes(jms): + ''' + Import all the nodes from a jms into the scene and returns a list of them. + ''' + scene_nodes = [] + for node in jms.nodes: + scene_node = create_sphere(name=node.name, size=0.02) + + # Assign parent if index is valid. + if node.parent_index >= 0: + scene_node.parent = scene_nodes[node.parent_index] + + # Set the translations + scene_node.rotation_mode = 'QUATERNION' + scene_node.rotation_quaternion = ( + node.rot_w, node.rot_i, node.rot_j, node.rot_k + ) + # TODO: Get proper position using Keanu Reeves math + # Undo 100x scaling from jms files. + scene_node.location = (node.pos_x/100, node.pos_y/100, node.pos_z/100) + + + + scene_nodes.append(scene_node) + + return scene_nodes + # The next is copied from mozzarilla.windows.tag_converters.model_converter # Too many references to libraries that aren't even used by the function diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index c599fbb..02ecb36 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -3,6 +3,8 @@ from bpy.props import BoolProperty, FloatProperty, StringProperty, EnumProperty from bpy_extras.io_utils import ImportHelper, ExportHelper, orientation_helper, path_reference_mode, axis_conversion +from ...halo1.model import read_halo1model, import_halo1_nodes + #@orientation_helper(axis_forward='-Z') Find the right value for this. class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): """ @@ -57,6 +59,13 @@ def execute(self, context): print("Use armatures: " + str(self.use_armatures)) print("Scale Enum: " + str(self.scale_enum)) print("Scale Float: " + str(self.scale_float)) + + # Test if jms import function doesn't crash. + jms = read_halo1model(self.filepath) + + # Import nodes into the scene. + import_halo1_nodes(jms) + return {'FINISHED'} def draw(self, context): From 6206d7c5636a7e35b478e18b53469effef61e391 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 30 Aug 2019 13:29:51 +0200 Subject: [PATCH 15/35] Fix comment. --- halo1/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo1/model.py b/halo1/model.py index c120076..d08709a 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -60,12 +60,12 @@ def import_halo1_nodes(jms): if node.parent_index >= 0: scene_node.parent = scene_nodes[node.parent_index] + # TODO: Get proper position and rotation using Keanu Reeves math # Set the translations scene_node.rotation_mode = 'QUATERNION' scene_node.rotation_quaternion = ( node.rot_w, node.rot_i, node.rot_j, node.rot_k ) - # TODO: Get proper position using Keanu Reeves math # Undo 100x scaling from jms files. scene_node.location = (node.pos_x/100, node.pos_y/100, node.pos_z/100) From c34daff2955ef8116b8fb601db0ef845ec871d15 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sat, 31 Aug 2019 01:00:56 +0200 Subject: [PATCH 16/35] Fix import workaround (Thanks Moses) --- __init__.py | 3 +++ halo1/model.py | 9 ++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index 2236f89..94269d3 100644 --- a/__init__.py +++ b/__init__.py @@ -10,6 +10,9 @@ "category": "Import-Export" } +# Initialize the libraries module. +from . import lib + # Import all submodules. from .menu import topbar_dropdown from .menu.import_export import halo1_model diff --git a/halo1/model.py b/halo1/model.py index d08709a..c4d1e02 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -2,11 +2,10 @@ import os -# TODO: Figure out why I can't import these without ..lib. -from ..lib.reclaimer.hek.defs.mode import mode_def -from ..lib.reclaimer.hek.defs.mod2 import mod2_def -from ..lib.reclaimer.model.jms import read_jms -from ..lib.reclaimer.model.model_decompilation import extract_model +from reclaimer.hek.defs.mode import mode_def +from reclaimer.hek.defs.mod2 import mod2_def +from reclaimer.model.jms import read_jms +from reclaimer.model.model_decompilation import extract_model from ..scene.shapes import create_sphere From c50ecf17fcef23daed40d5e3dd36fe576cd70ca9 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sat, 31 Aug 2019 01:58:38 +0200 Subject: [PATCH 17/35] Finalize node import function for now. --- halo1/model.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index c4d1e02..1d6e330 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -47,28 +47,34 @@ def read_halo1model(filepath): return jms -def import_halo1_nodes(jms): +def import_halo1_nodes(jms, scale=1.0, node_size=0.02): ''' Import all the nodes from a jms into the scene and returns a list of them. ''' + # Jms are scaled at x100 halo. Undo that. + scale = scale/100 + scene_nodes = [] for node in jms.nodes: - scene_node = create_sphere(name=node.name, size=0.02) + scene_node = create_sphere(name=node.name, size=node_size) # Assign parent if index is valid. - if node.parent_index >= 0: + if node.parent_index in range(len(scene_nodes)): scene_node.parent = scene_nodes[node.parent_index] + print(scene_node.parent) - # TODO: Get proper position and rotation using Keanu Reeves math - # Set the translations + # Store original rotation mode. + rot_mode = scene_node.rotation_mode + # Set rotation mode to quaternion and apply the rotation. scene_node.rotation_mode = 'QUATERNION' scene_node.rotation_quaternion = ( node.rot_w, node.rot_i, node.rot_j, node.rot_k ) - # Undo 100x scaling from jms files. - scene_node.location = (node.pos_x/100, node.pos_y/100, node.pos_z/100) - + # Set rotation mode back. + scene_node.rotation_mode = rot_mode + # Undo 100x scaling from jms files. + scene_node.location = (node.pos_x/scale, node.pos_y/scale, node.pos_z/scale) scene_nodes.append(scene_node) From feeb7924894b82f3a0b6d7436752894e4b712e0e Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sat, 31 Aug 2019 02:05:23 +0200 Subject: [PATCH 18/35] Make import store the return value of import_halo1_nodes() --- menu/import_export/halo1_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index 02ecb36..29c1ee5 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -64,7 +64,7 @@ def execute(self, context): jms = read_halo1model(self.filepath) # Import nodes into the scene. - import_halo1_nodes(jms) + nodes = import_halo1_nodes(jms) return {'FINISHED'} From 9c0c30c91af24570e546db5cf7e638e761b4b7d2 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 1 Sep 2019 11:06:54 +0200 Subject: [PATCH 19/35] Fix scaling. --- halo1/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo1/model.py b/halo1/model.py index 1d6e330..f511eee 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -74,7 +74,7 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): scene_node.rotation_mode = rot_mode # Undo 100x scaling from jms files. - scene_node.location = (node.pos_x/scale, node.pos_y/scale, node.pos_z/scale) + scene_node.location = (node.pos_x*scale, node.pos_y*scale, node.pos_z*scale) scene_nodes.append(scene_node) From 8fff14714fea2e66c6d87a4c2ce27a7d6149d6d8 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 14:00:05 +0100 Subject: [PATCH 20/35] Invert W component when importing nodes --- halo1/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo1/model.py b/halo1/model.py index f511eee..15b0b38 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -68,7 +68,7 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): # Set rotation mode to quaternion and apply the rotation. scene_node.rotation_mode = 'QUATERNION' scene_node.rotation_quaternion = ( - node.rot_w, node.rot_i, node.rot_j, node.rot_k + -node.rot_w, node.rot_i, node.rot_j, node.rot_k ) # Set rotation mode back. scene_node.rotation_mode = rot_mode From 03a61298a1daa35f9c49808b2417c3afb8fc78fa Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 14:35:54 +0100 Subject: [PATCH 21/35] Use scale_enum --- halo1/model.py | 8 +++----- menu/import_export/halo1_model.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index 15b0b38..01b844b 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -12,9 +12,6 @@ def read_halo1model(filepath): '''Takes a halo1 model file and turns it into a jms class.''' - # Workaround for reclaimer's brokenness on Linux - filepath = os.path.realpath(filepath) - if filepath.endswith('.gbxmodel'): # Load Gbxmodel mod2 = mod2_def.build(filepath=filepath) @@ -61,7 +58,6 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): # Assign parent if index is valid. if node.parent_index in range(len(scene_nodes)): scene_node.parent = scene_nodes[node.parent_index] - print(scene_node.parent) # Store original rotation mode. rot_mode = scene_node.rotation_mode @@ -74,7 +70,9 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): scene_node.rotation_mode = rot_mode # Undo 100x scaling from jms files. - scene_node.location = (node.pos_x*scale, node.pos_y*scale, node.pos_z*scale) + scene_node.location = ( + node.pos_x*scale, node.pos_y*scale, node.pos_z*scale + ) scene_nodes.append(scene_node) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index 29c1ee5..6137cc4 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -60,11 +60,25 @@ def execute(self, context): print("Scale Enum: " + str(self.scale_enum)) print("Scale Float: " + str(self.scale_float)) + # Set appropriate scaling + # TODO: All of these need constants. + scale = 1.0 + if self.scale_enum == 'METRIC': + scale = 10.0/0.032808/100.0 + elif self.scale_enum == 'MAX': + scale = 100 + elif self.scale_enum == 'HALO': + scale = 1.0 + elif self.scale_enum == 'CUSTOM': + scale = self.scale_float + else: + raise ValueError('Invalid scale_enum state.') + print("Effective scale:", scale) # Test if jms import function doesn't crash. jms = read_halo1model(self.filepath) # Import nodes into the scene. - nodes = import_halo1_nodes(jms) + nodes = import_halo1_nodes(jms, scale=scale) return {'FINISHED'} From 6e108b143d887533a115b4a435c7196f3cc47f15 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 14:58:36 +0100 Subject: [PATCH 22/35] Remove the need for the copied code segment from Mozzarilla --- halo1/model.py | 140 +++++-------------------------------------------- 1 file changed, 12 insertions(+), 128 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index 01b844b..ec16405 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -1,7 +1,5 @@ import bpy -import os - from reclaimer.hek.defs.mode import mode_def from reclaimer.hek.defs.mod2 import mod2_def from reclaimer.model.jms import read_jms @@ -12,24 +10,19 @@ def read_halo1model(filepath): '''Takes a halo1 model file and turns it into a jms class.''' - if filepath.endswith('.gbxmodel'): - # Load Gbxmodel - mod2 = mod2_def.build(filepath=filepath) + # These two model types can be imported the same way because of their + # nearly matching structures when built into a python object. + if (filepath.lower().endswith('.gbxmodel') + or filepath.lower().endswith('.model')): + # Load model + tag = None + if filepath.lower().endswith('.gbxmodel'): + tag = mod2_def.build(filepath=filepath) + else: + tag = mode_def.build(filepath=filepath) #TODO: Get all lod permutations. #Only getting the superhigh perms for now - jms = extract_model(mod2.data.tagdata, write_jms=False)[0] - return jms - - if filepath.endswith('.model'): - # Load Xbox model - mode = mode_def.build(filepath=filepath) - # Convert the Xbox model to a Gbxmodel - mod2 = mod2_def.build() - convert_model(mod2, mode, True) #True means: convert to mod2 - #TODO: Get all lod permutations. - #Only getting the superhigh perms for now - jms = extract_model(mod2.data.tagdata, write_jms=False)[0] - + jms = extract_model(tag.data.tagdata, write_jms=False)[0] return jms if filepath.endswith('.jms'): @@ -53,7 +46,7 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): scene_nodes = [] for node in jms.nodes: - scene_node = create_sphere(name=node.name, size=node_size) + scene_node = create_sphere(name="@"+node.name, size=node_size) # Assign parent if index is valid. if node.parent_index in range(len(scene_nodes)): @@ -77,112 +70,3 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): scene_nodes.append(scene_node) return scene_nodes - - -# The next is copied from mozzarilla.windows.tag_converters.model_converter -# Too many references to libraries that aren't even used by the function -# that we're importing. -# It relied on threadsafe_tkinter, which relies on tkinter, -# which relies on who knows what. - -# Just ignore this whole part for now - -from reclaimer.hek.defs.mod2 import fast_mod2_def -from reclaimer.stubbs.defs.mode import fast_mode_def - -def convert_model(src_tag, dst_tag, to_gbxmodel): - src_tag_data = src_tag.data.tagdata - dst_tag_data = dst_tag.data.tagdata - - # move the first 14 header fields from src tag into dst tag - # (except for the flags since usually ZONER shouldnt be copied) - dst_tag_data[1: 14] = src_tag_data[1: 14] - for flag_name in src_tag_data.flags.NAME_MAP: - if hasattr(dst_tag_data.flags, flag_name): - dst_tag_data.flags[flag_name] = src_tag_data.flags[flag_name] - - # fix the fact the mode and mod2 store stuff related to lods - # in reverse on most platforms(pc stubbs is an exception) - if dst_tag_data.superhigh_lod_cutoff < dst_tag_data.superlow_lod_cutoff: - tmp0 = dst_tag_data.superhigh_lod_cutoff - tmp1 = dst_tag_data.high_lod_cutoff - dst_tag_data.superhigh_lod_cutoff = dst_tag_data.superlow_lod_cutoff - dst_tag_data.high_lod_cutoff = dst_tag_data.low_lod_cutoff - dst_tag_data.low_lod_cutoff = tmp1 - dst_tag_data.superlow_lod_cutoff = tmp0 - - tmp0 = dst_tag_data.superhigh_lod_nodes - tmp1 = dst_tag_data.high_lod_nodes - dst_tag_data.superhigh_lod_nodes = dst_tag_data.superlow_lod_nodes - dst_tag_data.high_lod_nodes = dst_tag_data.low_lod_nodes - dst_tag_data.low_lod_nodes = tmp1 - dst_tag_data.superlow_lod_nodes = tmp0 - - # make all markers global ones - if hasattr(src_tag, "globalize_local_markers"): - src_tag.globalize_local_markers() - - # move the markers, nodes, regions, and shaders, from mode into mod2 - dst_tag_data.markers = src_tag_data.markers - dst_tag_data.nodes = src_tag_data.nodes - dst_tag_data.regions = src_tag_data.regions - dst_tag_data.shaders = src_tag_data.shaders - - # give the mod2 as many geometries as the mode - src_tag_geoms = src_tag_data.geometries.STEPTREE - dst_tag_geoms = dst_tag_data.geometries.STEPTREE - dst_tag_geoms.extend(len(src_tag_geoms)) - - # copy the data from the src_tag_geoms into the dst_tag_geoms - for i in range(len(dst_tag_geoms)): - # give the dst_tag_geom as many parts as the src_tag_geom - src_tag_parts = src_tag_geoms[i].parts.STEPTREE - dst_tag_parts = dst_tag_geoms[i].parts.STEPTREE - dst_tag_parts.extend(len(src_tag_parts)) - - # copy the data from the src_tag_parts into the dst_tag_parts - for j in range(len(dst_tag_parts)): - src_tag_part = src_tag_parts[j] - dst_tag_part = dst_tag_parts[j] - - # move the first 9 part fields from src_tag into dst_tag - # (except for the flags since usually ZONER shouldnt be copied) - dst_tag_part[1: 9] = src_tag_part[1: 9] - - src_local_nodes = getattr(src_tag_part, "local_nodes", None) - dst_local_nodes = getattr(dst_tag_part, "local_nodes", None) - if not getattr(src_tag_part.flags, "ZONER", False): - src_local_nodes = None - - if dst_local_nodes and src_local_nodes: - # converting from a gbxmodel with local nodes to a gbxmodel - # with local nodes. copy the local nodes and node count - dst_tag_part.flags.ZONER = True - dst_tag_part.local_node_count = src_tag_part.local_node_count - dst_tag_part.local_nodes[:] = src_local_nodes[:] - elif src_local_nodes: - # converting from a gbxmodel with local nodes to - # something without them. make the nodes absolute. - src_tag.delocalize_part_nodes(i, j) - - # move the vertices and triangles from the src_tag into the dst_tag - dst_tag_part.triangles = src_tag_part.triangles - dst_tag_part.uncompressed_vertices = src_tag_part.uncompressed_vertices - dst_tag_part.compressed_vertices = src_tag_part.compressed_vertices - - uncomp_verts = dst_tag_part.uncompressed_vertices - comp_verts = dst_tag_part.compressed_vertices - - if to_gbxmodel: - # if the compressed vertices are valid or - # the uncompressed are not then we don't have - # any conversion to do(already uncompressed) - if not uncomp_verts.size or comp_verts.size: - dst_tag.decompress_part_verts(i, j) - elif not comp_verts.size or uncomp_verts.size: - # the uncompressed vertices are valid or - # the compressed are not, so we don't have - # any conversion to do(already compressed) - dst_tag.compress_part_verts(i, j) - - dst_tag.calc_internal_data() From fed75d2fce6cafe88913d350c1586dde24fa53cd Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 15:08:23 +0100 Subject: [PATCH 23/35] Remove debug prints --- menu/import_export/halo1_model.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index 6137cc4..bbf7212 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -55,11 +55,6 @@ class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): ) def execute(self, context): - print("Use nodes: " + str(self.use_nodes)) - print("Use armatures: " + str(self.use_armatures)) - print("Scale Enum: " + str(self.scale_enum)) - print("Scale Float: " + str(self.scale_float)) - # Set appropriate scaling # TODO: All of these need constants. scale = 1.0 @@ -73,7 +68,7 @@ def execute(self, context): scale = self.scale_float else: raise ValueError('Invalid scale_enum state.') - print("Effective scale:", scale) + # Test if jms import function doesn't crash. jms = read_halo1model(self.filepath) From 90118958364848c3931df32f31f11e0eb7358093 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 15:08:48 +0100 Subject: [PATCH 24/35] Add node_size property and minimum values for sizes --- menu/import_export/halo1_model.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index bbf7212..1a24239 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -31,6 +31,12 @@ class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): description="Import the nodes/frames.", default=True, ) + node_size: FloatProperty( + name="Node Scene Size", + description="Set the size that nodes should have in the Blender scene if at 1.0 scale.", + default=0.1, + min=0.0, + ) use_armatures: BoolProperty( name="Import Nodes as Armature", description="Import the nodes as armature.", @@ -52,6 +58,7 @@ class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): name="Custom Scale", description="Set your own scale.", default=1.0, + min=0.0, ) def execute(self, context): @@ -73,7 +80,7 @@ def execute(self, context): jms = read_halo1model(self.filepath) # Import nodes into the scene. - nodes = import_halo1_nodes(jms, scale=scale) + nodes = import_halo1_nodes(jms, scale=scale, node_size=self.node_size) return {'FINISHED'} @@ -87,6 +94,7 @@ def draw(self, context): row.label(text="Nodes:") row.prop(self, "use_nodes") if self.use_nodes: + box.prop(self, "node_size") box.prop(self, "use_armatures") # Scale settings elements: From e40ac5b710a8be43be66788ee9074e49583f13ea Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 15:11:56 +0100 Subject: [PATCH 25/35] Improve error for non h1 jms. Case-insensitively check file extension --- halo1/model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index ec16405..c63bddd 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -25,7 +25,7 @@ def read_halo1model(filepath): jms = extract_model(tag.data.tagdata, write_jms=False)[0] return jms - if filepath.endswith('.jms'): + if filepath.lower().endswith('.jms'): # Read jms file into string. jms_string = "" with open(filepath, 'r') as jms_file: @@ -33,7 +33,8 @@ def read_halo1model(filepath): # Read Jms data from string. jms = read_jms(jms_string) # Make sure it's a Halo 1 jms - assert(jms.version == "8200") + if jms.version != "8200": + raise ValueError('Not a Halo 1 jms!') return jms From b66d3311884018ee5d992daa4ae98af4682b2923 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 15:13:07 +0100 Subject: [PATCH 26/35] Correct docstring --- halo1/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo1/model.py b/halo1/model.py index c63bddd..e3a9967 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -8,7 +8,7 @@ from ..scene.shapes import create_sphere def read_halo1model(filepath): - '''Takes a halo1 model file and turns it into a jms class.''' + '''Takes a halo1 model file and turns it into a jms object.''' # These two model types can be imported the same way because of their # nearly matching structures when built into a python object. From 194361be938cb4ef08b7b76da6f88c99fe65e259 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sun, 8 Dec 2019 17:37:06 +0100 Subject: [PATCH 27/35] Add a TODO --- halo1/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/halo1/model.py b/halo1/model.py index e3a9967..bdf728f 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -10,6 +10,9 @@ def read_halo1model(filepath): '''Takes a halo1 model file and turns it into a jms object.''' + # TODO: Use a tag handler to see if these files actually are what they + # say they are. We can get really nasty parsing problems if they aren't. + # These two model types can be imported the same way because of their # nearly matching structures when built into a python object. if (filepath.lower().endswith('.gbxmodel') From 46d61b912807a45bc2139b64bc73856e052905dd Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Tue, 10 Dec 2019 02:44:02 +0100 Subject: [PATCH 28/35] Make modules register through a list. Indirectly fix a module being registered instead of unregistered. --- __init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index 94269d3..adfea11 100644 --- a/__init__.py +++ b/__init__.py @@ -17,21 +17,27 @@ from .menu import topbar_dropdown from .menu.import_export import halo1_model +modules = [ + halo1_model, + topbar_dropdown, +] + def register(): ''' Registers classes on load by calling the register functions in their respective modules. ''' - halo1_model.register() - topbar_dropdown.register() + for module in modules: + module.register() def unregister(): ''' Unregisters classes on unload by calling the unregister functions in their respective modules. ''' - topbar_dropdown.unregister() - halo1_model.register() + #Unregister classes in reverse order to avoid any dependency problems. + for module in reverse(modules): + module.unregister() if __name__ == "__main__": register() From 80292de6079a3b69192c21e1233f90fe08ffc811 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Tue, 31 Dec 2019 01:08:59 +0100 Subject: [PATCH 29/35] Move scale enum values to constants file --- constants.py | 13 +++++++++++++ menu/import_export/halo1_model.py | 11 ++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 constants.py diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..025291e --- /dev/null +++ b/constants.py @@ -0,0 +1,13 @@ + +# These are used to figure out what amount something needs +# to be scaled when importing or exporting. +SCALE_MULTIPLIERS = { + # 10 : A world unit is 10 feet. + # 0.032808 : feet per centimeter + # 100.0 : centimeters per meter. + 'METRIC' : 10.0/0.032808/100.0, + # Halo models in 3dsmax as 100x as big as internally. + 'MAX' : 100.0, + # Base scale. Used for straight conversions. + 'HALO' : 1.0, +} diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index 1a24239..c0cb040 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -4,6 +4,7 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper, orientation_helper, path_reference_mode, axis_conversion from ...halo1.model import read_halo1model, import_halo1_nodes +from ...constants import SCALE_MULTIPLIERS #@orientation_helper(axis_forward='-Z') Find the right value for this. class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): @@ -51,7 +52,7 @@ class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): ('METRIC', "Blender", "Use Blender's metric scaling."), ('MAX', "3ds Max", "Use 3dsmax's 100xHalo scale."), ('HALO', "Internal", "Use Halo's internal 1.0 scale (small)."), - ('CUSTOM', "Custom", "Set your own scaling."), + ('CUSTOM', "Custom", "Set your own scaling multiplier."), ) ) scale_float: FloatProperty( @@ -65,12 +66,8 @@ def execute(self, context): # Set appropriate scaling # TODO: All of these need constants. scale = 1.0 - if self.scale_enum == 'METRIC': - scale = 10.0/0.032808/100.0 - elif self.scale_enum == 'MAX': - scale = 100 - elif self.scale_enum == 'HALO': - scale = 1.0 + if self.scale_enum in SCALE_MULTIPLIERS: + scale = SCALE_MULTIPLIERS[self.scale_enum] elif self.scale_enum == 'CUSTOM': scale = self.scale_float else: From b57eebd211f3bdc91a5eb2a0f064decf2d9f9bab Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Tue, 31 Dec 2019 01:12:56 +0100 Subject: [PATCH 30/35] Return imported nodes as dict instead of list --- halo1/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index bdf728f..c875c12 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -48,8 +48,9 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): # Jms are scaled at x100 halo. Undo that. scale = scale/100 - scene_nodes = [] - for node in jms.nodes: + scene_nodes = dict() + for i in range(len(jms.nodes)): + node = jms.nodes[i] scene_node = create_sphere(name="@"+node.name, size=node_size) # Assign parent if index is valid. @@ -71,6 +72,6 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): node.pos_x*scale, node.pos_y*scale, node.pos_z*scale ) - scene_nodes.append(scene_node) + scene_nodes[i] = scene_node return scene_nodes From 45f0c4b6b27d2aba99213fbadfb8b0e11e51dd40 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Tue, 31 Dec 2019 01:30:31 +0100 Subject: [PATCH 31/35] Remove todo --- menu/import_export/halo1_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index c0cb040..6854139 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -64,7 +64,6 @@ class MT_krieg_ImportHalo1Model(bpy.types.Operator, ImportHelper): def execute(self, context): # Set appropriate scaling - # TODO: All of these need constants. scale = 1.0 if self.scale_enum in SCALE_MULTIPLIERS: scale = SCALE_MULTIPLIERS[self.scale_enum] From 144bf203739bc6a9a279f140aaa224a4108020de Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Sat, 4 Jan 2020 11:01:50 +0100 Subject: [PATCH 32/35] Use enumerate --- halo1/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/halo1/model.py b/halo1/model.py index c875c12..cca1d16 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -49,8 +49,7 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): scale = scale/100 scene_nodes = dict() - for i in range(len(jms.nodes)): - node = jms.nodes[i] + for i, node in enumerate(jms.nodes): scene_node = create_sphere(name="@"+node.name, size=node_size) # Assign parent if index is valid. From 2c9bfcf37082db7564733f01a5a0539a9482efa3 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 10 Jan 2020 07:21:33 +0100 Subject: [PATCH 33/35] Use JmsModel scale as base scale --- constants.py | 15 +++++++-------- halo1/model.py | 3 --- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/constants.py b/constants.py index 025291e..844c0cb 100644 --- a/constants.py +++ b/constants.py @@ -1,13 +1,12 @@ # These are used to figure out what amount something needs # to be scaled when importing or exporting. +# These scales are based on the JmsModel's base scale. Which is 100x Halo. SCALE_MULTIPLIERS = { - # 10 : A world unit is 10 feet. - # 0.032808 : feet per centimeter - # 100.0 : centimeters per meter. - 'METRIC' : 10.0/0.032808/100.0, - # Halo models in 3dsmax as 100x as big as internally. - 'MAX' : 100.0, - # Base scale. Used for straight conversions. - 'HALO' : 1.0, + # One foot is 30.48cm. 1 Jms unit is 1/10 of a foot. + 'METRIC' : 3.048, + # Halo models in 3dsmax are 1:1 to Jms + 'MAX' : 1.0, + # Jms models have 100x the scaling of internal Halo models. + 'HALO' : 0.01, } diff --git a/halo1/model.py b/halo1/model.py index cca1d16..3fb669e 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -45,9 +45,6 @@ def import_halo1_nodes(jms, scale=1.0, node_size=0.02): ''' Import all the nodes from a jms into the scene and returns a list of them. ''' - # Jms are scaled at x100 halo. Undo that. - scale = scale/100 - scene_nodes = dict() for i, node in enumerate(jms.nodes): scene_node = create_sphere(name="@"+node.name, size=node_size) From 40c2d123e544ed5788ca565c4ee8dfc752f07f1b Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 10 Jan 2020 07:21:56 +0100 Subject: [PATCH 34/35] Add constants for JmsModel versions --- constants.py | 3 +++ halo1/model.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/constants.py b/constants.py index 844c0cb..f118688 100644 --- a/constants.py +++ b/constants.py @@ -10,3 +10,6 @@ # Jms models have 100x the scaling of internal Halo models. 'HALO' : 0.01, } + +JMS_VERSION_HALO_1 = "8200" +JMS_VERSION_HALO_2 = "8210" diff --git a/halo1/model.py b/halo1/model.py index 3fb669e..6f01dd6 100644 --- a/halo1/model.py +++ b/halo1/model.py @@ -5,6 +5,7 @@ from reclaimer.model.jms import read_jms from reclaimer.model.model_decompilation import extract_model +from ..constants import JMS_VERSION_HALO_1 from ..scene.shapes import create_sphere def read_halo1model(filepath): @@ -36,7 +37,7 @@ def read_halo1model(filepath): # Read Jms data from string. jms = read_jms(jms_string) # Make sure it's a Halo 1 jms - if jms.version != "8200": + if jms.version != JMS_VERSION_HALO_1: raise ValueError('Not a Halo 1 jms!') return jms From 506b4b0e461ab1bd949449656b4b15876d03c674 Mon Sep 17 00:00:00 2001 From: gbMichelle Date: Fri, 10 Jan 2020 07:22:54 +0100 Subject: [PATCH 35/35] Improve unregister comment --- menu/import_export/halo1_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/menu/import_export/halo1_model.py b/menu/import_export/halo1_model.py index 6854139..979cd4c 100644 --- a/menu/import_export/halo1_model.py +++ b/menu/import_export/halo1_model.py @@ -116,7 +116,8 @@ def register(): def unregister(): - for cls in reversed(classes): #Reversed because: first in, last out. + #Unregister classes in reverse order to avoid any dependency problems. + for cls in reversed(classes): unregister_class(cls)