mirror of
https://github.com/Theaninova/CraftStudioExport.git
synced 2025-12-10 08:26:19 +00:00
186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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()
|