commit e8615a57fa04da0af739946b6aed2e571653fb97 Author: Thea Schöbl Date: Fri Aug 2 06:47:33 2019 +0200 Initial commit diff --git a/.idea/CraftStudioExport.iml b/.idea/CraftStudioExport.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/CraftStudioExport.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a2e120d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..caa622e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a3d052d --- /dev/null +++ b/__init__.py @@ -0,0 +1,185 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTIBILITY 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, see . + +bl_info = { + "name" : "CraftStudio (.csmodel)", + "author" : "Thea Schöbl", + "blender" : (2, 80, 0), + "version" : (0, 0, 1), + "location" : "File > Export > CraftStudio (.csmodel)", + "description" : "Export mesh to CraftStudio Model (.csmodel)", + "warning" : "", + "category" : "Import-Export" +} + +import bpy +from bpy_extras.io_utils import ExportHelper +from mathutils import Vector +import time +import struct + +FILE_SIGNATURE = bytes([0x00, 0x05, 0x00]) + +# This is where the magic happenes +def do_export(context, props, filepath): + file = open(filepath, "wb") + + file.write(FILE_SIGNATURE) #File signature + + model_collection = bpy.data.collections['CSMODEL'] + current_id = 0 + + #TODO: determine the amount of blocks + amount_of_blocks = len(model_collection.all_objects) + file.write(amount_of_blocks.to_bytes(2, byteorder='little', signed=False)) + file.write(amount_of_blocks.to_bytes(2, byteorder='little', signed=False)) + #Yes, we actually need it twice for whatever reason. I didn't invent this format. + + name_id_dict = {} + name_pivot_dict = {} + + for obj in model_collection.all_objects: + name_id_dict[obj.data.name] = current_id #Save the object to the dictonary for parenting + name_pivot_dict[obj.data.name] = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) #Calculate the center of the object + current_id = current_id + 1 #Increment ID, as each one is unique + + #START DOING BLOCK STUFF + for obj in model_collection.all_objects: + file.write(name_id_dict[obj.data.name].to_bytes(2, byteorder='little', signed=False)) #Write the ID + + #Check Parents + if obj.parent is not None and obj.parent.data is not None: + #Check and write parent ID + file.write(name_id_dict[obj.parent.data.name].to_bytes(2, byteorder='little', signed=False)) + else: + file.write(0xFFFF.to_bytes(2, byteorder='little', signed=False)) #No parent + + #Write Name + file.write(len(obj.data.name).to_bytes(1, byteorder='little', signed=False)) + if len(obj.data.name) >= 0xFF: #TODO: check if that is correct + print('FATAL ERROR: name is longer than 16 characters') + return False + file.write(str.encode(obj.data.name)) + + #Write Position + obj_location = obj.location if obj.parent is None or obj.parent.data is None else obj.location - name_pivot_dict[obj.parent.data.name] + for val in obj_location: + file.write(bytearray(struct.pack("f", val/16))) + + #Write Pivot Offset + object_center = name_pivot_dict[obj.data.name] + for val in object_center: + file.write(bytearray(struct.pack("f", val/16))) + + #Size as Float + size = obj.dimensions #Just take the dimensions + for val in size: + file.write(bytearray(struct.pack("f", val))) + + #Rotation + quaternion = obj.rotation_euler.to_quaternion() + for val in quaternion: + file.write(bytearray(struct.pack("f", val))) + + #TODO: Proper Size + block_size = [1, 1, 1] + for val in block_size: + file.write(val.to_bytes(2, byteorder='little', signed=False)) + + file.write(0x1.to_bytes(1, byteorder='little', signed=False)) + + #TODO: Proper UV Map + def_uv = [0, 0] + for i in range(0, 6): + for val in def_uv: + file.write(val.to_bytes(4, byteorder='little', signed=False)) #No parent + + file.write(0x000000.to_bytes(3, byteorder='little', signed=False)) #TODO: figure out what this does + file.write(0x000000.to_bytes(3, byteorder='little', signed=False)) #TODO: figure out what this does + + #End of file or something like that, idk. + file.write(0xB500.to_bytes(2, byteorder='big', signed=False)) #DON'T CHANGE BYTE ORDER + file.write(0x0000.to_bytes(2, byteorder='little', signed=False)) + + file.flush() + file.close() + + return True + +class Export_csmodel(bpy.types.Operator, ExportHelper): + """Export the active Object as a .csmodel file""" + bl_idname = "export_shape.csmodel" + bl_label = "Export CraftStudio Model (.csmodel)" + + filename_ext = ".csmodel" + + @classmethod + def poll(cls, context): + obj = context.active_object + return ( + obj is not None + and obj.type in {'MESH'} + ) + + def execute(self, context): + start_time = time.time() + + props = self.properties + filepath = self.filepath + filepath = bpy.path.ensure_ext(filepath, self.filename_ext) + + exported = do_export(context, props, filepath) + + if exported: + print('finished export in %s seconds' % + ((time.time() - start_time))) + print(filepath) + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + + if True: + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + elif True: + wm.invoke_search_popup(self) + return {'RUNNING_MODAL'} + elif False: + return wm.invoke_props_popup(self, event) + elif False: + return self.execute(context) + +def menu_func_export_button(self, context): + self.layout.operator(Export_csmodel.bl_idname, text="CraftStudio Model (.csmodel)") + +classes = [ + Export_csmodel, +] + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.TOPBAR_MT_file_export.append(menu_func_export_button) + +def unregister(): + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_button) + + for cls in classes: + bpy.utils.unregister_class(cls) + + +if __name__ == "__main__": + register()