diff --git a/src/blender_addon/io_mesh_msh2/__init__.py b/src/blender_addon/io_mesh_msh2/__init__.py new file mode 100644 index 0000000..228e766 --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/__init__.py @@ -0,0 +1,228 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + + +from .msh2 import msh2 + + +msh = msh2.MSH2(None) +bl_info = { + "name": "Zero Editor MSH2 format", + "author": "Maxim Stewart", + "version": (0, 0, 1), + "blender": (2, 74, 0), + "location": "File > Import-Export", + "description": "Import-Export Zero Editor MSH2 models, meshs, and textures", + "warning": "", + "wiki_url": "https://schlechtwetterfront.github.io/ze_filetypes/msh.html", + "category": "Import-Export"} + +if "bpy" in locals(): + import importlib + if "import_msh2" in locals(): + importlib.reload(import_msh2) + if "export_msh2" in locals(): + importlib.reload(export_msh2) + + +import bpy +from bpy.props import ( + CollectionProperty, + BoolProperty, + FloatProperty, + StringProperty, + EnumProperty, + ) +from bpy_extras.io_utils import ( + ImportHelper, + ExportHelper, + orientation_helper_factory, + path_reference_mode, + axis_conversion, + ) + +from bpy.types import OperatorFileListElement + +# NOTE: NEED to see what ZERO Engine uses as forward +IOOBJOrientationHelper = orientation_helper_factory("IOOBJOrientationHelper", axis_forward='-Z', axis_up='Y') + + +class ImportMSH2(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): + """Load a MSH2 File""" + bl_idname = "import_zero_editor_msh2.msh" + bl_label = "Zero Editor Import MSH2" + bl_options = {'REGISTER', 'UNDO'} + + filename_ext = ".msh" + filter_glob = StringProperty( + default = "*.msh;*.tga", + options = {'HIDDEN'}, + ) + + files = CollectionProperty( + name="File Path", + type=OperatorFileListElement, + ) + directory = StringProperty( + subtype='DIR_PATH', + ) + + + use_image_search = BoolProperty( + name="Image Search", + description="Search subdirs for any associated images " + "(Warning, may be slow)", + default=True, + ) + + def execute(self, context): + keywords = self.as_keywords(ignore=("axis_forward", + "axis_up", + "filter_glob", + "split_mode", + )) + + keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES') + if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths: + import os + keywords["relpath"] = os.path.dirname(bpy.data.filepath) + + data = {**keywords} + msh.import_file(data["filepath"]) + return {'FINISHED'} + + + # def draw(self, context): + # layout = self.layout + # + # row = layout.row(align=True) + # row.prop(self, "use_smooth_groups") + # row.prop(self, "use_edges") + # + # box = layout.box() + # row = box.row() + # row.prop(self, "split_mode", expand=True) + # + # row = box.row() + # if self.split_mode == 'ON': + # row.label(text="Split by:") + # row.prop(self, "use_split_objects") + # row.prop(self, "use_split_groups") + # else: + # row.prop(self, "use_groups_as_vgroups") + # + # row = layout.split(percentage=0.67) + # row.prop(self, "global_clamp_size") + # layout.prop(self, "axis_forward") + # layout.prop(self, "axis_up") + # + # layout.prop(self, "use_image_search") + + +class ExportMSH2(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper): + """Save a MSH2 File""" + + bl_idname = "export_zero_editor_msh2.msh" + bl_label = 'Zero Editor Export MSH2' + + # # context group + filename_ext = ".msh" + filter_glob = StringProperty( + default = "*.msh;*.tga", + options = {'HIDDEN'}, + ) + + use_selection = BoolProperty( + name = "Selection Only", + description = "Export selected objects only", + default = False, + ) + + use_animation = BoolProperty( + name="Animation", + description="Write out an OBJ for each frame", + default=False, + ) + + use_uvs = BoolProperty( + name="Include UVs", + description="Write out the active UV coordinates", + default=True, + ) + use_materials = BoolProperty( + name="Write Materials", + description="Write out the MTL file", + default=True, + ) + use_triangles = BoolProperty( + name="Triangulate Faces", + description="Convert all faces to triangles", + default=False, + ) + + def execute(self, context): + keywords = self.as_keywords(ignore=("axis_forward", + "axis_up", + "global_scale", + "check_existing", + "filter_glob", + )) + data = {**keywords} + # msh.export_file() + # print("") + # print("") + # print(context) + # print("") + # print("") + msh.export_file(data["filepath"]) + return {'FINISHED'} + + + + + +classes = ( + ImportMSH2, + ExportMSH2, +) + + +def menu_import(self, context): + self.layout.operator(ImportMSH2.bl_idname, text="Zero Editor MSH2 (.msh)") + + +def menu_export(self, context): + self.layout.operator(ExportMSH2.bl_idname, text="Zero Editor MSH2 (.msh)") + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.INFO_MT_file_import.append(menu_import) + bpy.types.INFO_MT_file_export.append(menu_export) + + +def unregister(): + bpy.types.INFO_MT_file_import.remove(menu_import) + bpy.types.INFO_MT_file_export.remove(menu_export) + + for cls in classes: + bpy.utils.unregister_class(cls) + + +if __name__ == "__main__": + register() diff --git a/src/blender_addon/io_mesh_msh2/cli.py b/src/blender_addon/io_mesh_msh2/cli.py new file mode 100644 index 0000000..9632341 --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/cli.py @@ -0,0 +1,13 @@ +# from msh2.MSH2 import MSH2 +# +# msh2 = MSH2(None) + + +from msh2 import msh2 + + +msh = msh2.MSH2(None) + +msh.import_file("../../msh/all_weap_inf_lightsabre.msh") + +# msh.export_file("all_weap_inf_lightsabre.msh") diff --git a/src/blender_addon/io_mesh_msh2/export_msh2.py b/src/blender_addon/io_mesh_msh2/export_msh2.py new file mode 100644 index 0000000..91e9f2f --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/export_msh2.py @@ -0,0 +1,74 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +This script exports a MSH2 file from Blender. + +Usage: +Run this script from "File->Export" menu and then MSH2 file. +Note, This exports mesh objects and materials only, nurbs and curves are not supported. + +https://schlechtwetterfront.github.io/ze_filetypes/msh.html +""" + + +# def save(context, +# filepath, +# *, +# use_triangles=False, +# use_edges=True, +# use_normals=False, +# use_smooth_groups=False, +# use_smooth_groups_bitflags=False, +# use_uvs=True, +# use_materials=True, +# use_mesh_modifiers=True, +# use_mesh_modifiers_render=False, +# use_blen_objects=True, +# group_by_object=False, +# group_by_material=False, +# keep_vertex_order=False, +# use_vertex_groups=False, +# use_nurbs=True, +# use_selection=True, +# use_animation=False, +# global_matrix=None, +# path_mode='AUTO' +# ): +# +# _write(context, filepath, +# EXPORT_TRI=use_triangles, +# EXPORT_EDGES=use_edges, +# EXPORT_SMOOTH_GROUPS=use_smooth_groups, +# EXPORT_SMOOTH_GROUPS_BITFLAGS=use_smooth_groups_bitflags, +# EXPORT_NORMALS=use_normals, +# EXPORT_UV=use_uvs, +# EXPORT_MTL=use_materials, +# EXPORT_APPLY_MODIFIERS=use_mesh_modifiers, +# EXPORT_APPLY_MODIFIERS_RENDER=use_mesh_modifiers_render, +# EXPORT_BLEN_OBS=use_blen_objects, +# EXPORT_GROUP_BY_OB=group_by_object, +# EXPORT_GROUP_BY_MAT=group_by_material, +# EXPORT_KEEP_VERT_ORDER=keep_vertex_order, +# EXPORT_POLYGROUPS=use_vertex_groups, +# EXPORT_CURVE_AS_NURBS=use_nurbs, +# EXPORT_SEL_ONLY=use_selection, +# EXPORT_ANIMATION=use_animation, +# EXPORT_GLOBAL_MATRIX=global_matrix, +# EXPORT_PATH_MODE=path_mode, +# ) +# +# return {'FINISHED'} diff --git a/src/blender_addon/io_mesh_msh2/import_msh2.py b/src/blender_addon/io_mesh_msh2/import_msh2.py new file mode 100644 index 0000000..516d22e --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/import_msh2.py @@ -0,0 +1,48 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +This script imports a MSH2 files to Blender. + +Usage: +Run this script from "File->Import" menu and then load the desired MSH2 file. +Note, This loads mesh objects and materials only, nurbs and curves are not supported. + +https://schlechtwetterfront.github.io/ze_filetypes/msh.html +""" + + + +# def load(context, +# filepath, +# *, +# global_clamp_size=0.0, +# use_smooth_groups=True, +# use_edges=True, +# use_split_objects=True, +# use_split_groups=True, +# use_image_search=True, +# use_groups_as_vgroups=False, +# use_cycles=True, +# relpath=None, +# global_matrix=None +# ): +# """ +# Called by the user interface or another script. +# load_msh2(path) - should give acceptable results. +# This function passes the file and sends the data off +# to be split into objects and then converted into mesh objects +# """ diff --git a/src/blender_addon/io_mesh_msh2/msh2/Logger.py b/src/blender_addon/io_mesh_msh2/msh2/Logger.py new file mode 100644 index 0000000..fed0b0e --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/msh2/Logger.py @@ -0,0 +1,59 @@ +# Python imports +import os, logging + +# Application imports + + +class Logger: + def __init__(self, name = "NO_LOGGER_NAME_PASSED_ON_INIT"): + self.logger = self.create_logger(name) + + def get_logger(self): + return self.logger + + def create_logger(self, loggerName, createFile = True): + """ + Create a new logging object and return it. + :note: + NOSET # Don't know the actual log level of this... (defaulting or literally none?) + Log Levels (From least to most) + Type Value + CRITICAL 50 + ERROR 40 + WARNING 30 + INFO 20 + DEBUG 10 + :param loggerName: Sets the name of the logger object. (Used in log lines) + :param createFile: Whether we create a log file or just pump to terminal + + :return: the logging object we created + """ + + globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels + chLogLevel = logging.CRITICAL # Prety musch the only one we change ever + fhLogLevel = logging.DEBUG + log = logging.getLogger(loggerName) + log.setLevel(globalLogLvl) + + # Set our log output styles + fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S') + cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s') + + ch = logging.StreamHandler() + ch.setLevel(level=chLogLevel) + ch.setFormatter(cFormatter) + log.addHandler(ch) + + if createFile: + folder = "logs" + file = folder + "/flask-application.log" + + if not os.path.exists(folder): + os.mkdir(folder) + + fh = logging.FileHandler(file) + fh.setLevel(level=fhLogLevel) + fh.setFormatter(fFormatter) + log.addHandler(fh) + + return log diff --git a/src/blender_addon/io_mesh_msh2/msh2/__init__.py b/src/blender_addon/io_mesh_msh2/msh2/__init__.py new file mode 100644 index 0000000..b6e690f --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/msh2/__init__.py @@ -0,0 +1 @@ +from . import * diff --git a/src/blender_addon/io_mesh_msh2/msh2/__main__.py b/src/blender_addon/io_mesh_msh2/msh2/__main__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blender_addon/io_mesh_msh2/msh2/msh2.py b/src/blender_addon/io_mesh_msh2/msh2/msh2.py new file mode 100644 index 0000000..e179432 --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/msh2/msh2.py @@ -0,0 +1,37 @@ +# Python imports +import faulthandler +import traceback + +# Gtk imports + +# Application imports +from . import msh2_unpack + + +class MSH2(): + """docstring for MSH2.""" + + def __init__(self, arg): + faulthandler.enable() # For better debug info + self.arg = arg + self.msh_config = {'do_logging': True, 'ignore_geo': False, 'triangulate': False} + self.msh = None + + + def import_file(self, file_name): + try: + self.msh = msh2_unpack.MSHUnpack(file_name, self.msh_config).unpack() + except Exception as e: + traceback.print_exc() + + def export_file(self, file_name): + try: + self.msh.save(file_name) + except Exception as e: + traceback.print_exc() + + def export_file_to_binary_json(self, file_name): + try: + self.msh.save_json(file_name) + except Exception as e: + traceback.print_exc() diff --git a/src/blender_addon/io_mesh_msh2/msh2/msh2_crc.py b/src/blender_addon/io_mesh_msh2/msh2/msh2_crc.py new file mode 100644 index 0000000..2efca23 --- /dev/null +++ b/src/blender_addon/io_mesh_msh2/msh2/msh2_crc.py @@ -0,0 +1,154 @@ +''' + ZeroEngine .msh model format CRC algorithm. + + Refer to + schlechtwetterfront.github.io/ze_filetypes/msh.html + for more information regarding the file format. +''' + +# Python imports +from struct import pack + +# Gtk imports + +# Application imports + + +class CRCError(Exception): + def __init__(self, val): + self.val = val + + def __str__(self): + return '{0}'.format(self.val) + +# CRC lookup table. +TABLE_32 = ( + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, + 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, + 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, + 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, + 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, + 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, + 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, + 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, + 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, + 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, + 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, + 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, + 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, + 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, + 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, + 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, + 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, + 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, + 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, + 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, + 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, + 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, + 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, + 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, + 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, + 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, + 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, + 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, + 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, + 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, + 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, + 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, + 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, + 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, + 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, + 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, + 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, + 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, + 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, + 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, + 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, + 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, + 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, + 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, + 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, + 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, + 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, + 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, + 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, + 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, + 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, + 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, + 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, + 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, + 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, + 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, + 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, + 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, + 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, + 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, + 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, + 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 +) + +# Used to calculate the lowercase CRC. +TOLOWER = ( + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +) + + +def return_lowest_bits(n): + '''Simulate unsigned behavior.''' + return n & 0xFFFFFFFF + + +def crc(string): + '''Calculate the Zero CRC from string and return it as number.''' + crc_ = 0 + crc_ = return_lowest_bits(~crc_) + if string: + for char in string: + ind = (crc_ >> 24) + ind = ind ^ TOLOWER[ord(char)] + crc_ = return_lowest_bits(crc_ << 8) ^ TABLE_32[ind] + return return_lowest_bits(~crc_) + + +def strcrc(string): + '''Calculate the Zero CRC and return it in a structure usable in .msh files.''' + return pack('= i: + i += 4 + return string.ljust(i, b'\x00') + + def null_terminate(self, string): + '''Null-terminates the given string.''' + return string + b'\x00' + + def pack_long_chunk(self, header, number): + '''Chunk with only one long int.''' + data = [header.encode("ascii")] + data.append(struct.pack('