@ -5,14 +5,14 @@ import shutil
import struct
import struct
from mathutils import * ; from math import *
from mathutils import * ; from math import *
from bpy_extras . io_utils import ( axis_conversion )
from bpy_extras . io_utils import ( axis_conversion )
from dataclasses import dataclass
C = bpy . context
C = bpy . context
D = bpy . data
D = bpy . data
LEVEL_EXPORT_NAME = " level "
ROOMS_EXPORT_NAME = " rooms "
EXPORT_DIRECTORY = " ../assets/exported_3d "
EXPORT_DIRECTORY = " ../assets/exported_3d "
print ( " \n \n Let ' s get it started " )
if os . path . exists ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) ) :
if os . path . exists ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) ) :
shutil . rmtree ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) )
shutil . rmtree ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) )
@ -63,11 +63,6 @@ def normalize_joint_weights(weights):
return result
return result
# for the level.bin
# for the level.bin
level_object_data = [ ]
collision_cubes = [ ]
placed_entities = [ ]
saved_meshes = set ( )
mapping = axis_conversion (
mapping = axis_conversion (
from_forward = " Y " ,
from_forward = " Y " ,
from_up = " Z " ,
from_up = " Z " ,
@ -76,10 +71,6 @@ mapping = axis_conversion(
)
)
mapping . resize_4x4 ( )
mapping . resize_4x4 ( )
with open ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } /shorttest.bin " ) , " wb " ) as f :
for i in range ( 4 ) :
write_u16 ( f , i )
project_directory = bpy . path . abspath ( " // " )
project_directory = bpy . path . abspath ( " // " )
def is_file_in_project ( file_path ) :
def is_file_in_project ( file_path ) :
file_name = os . path . basename ( file_path )
file_name = os . path . basename ( file_path )
@ -90,22 +81,24 @@ def is_file_in_project(file_path):
saved_images = set ( )
saved_images = set ( )
def ensure_tex_saved_and_get_name ( o ) - > str :
def ensure_tex_saved_and_get_name ( o ) - > str :
""" returns the path to the mesh ' s texture ' s png in the exported directory """
""" returns the name of the mesh ' s texture ' s png in the exported directory, of the current object """
assert o . type == " MESH " , f " Object { o . name } isn ' t a mesh and is attempting to be saved as such "
mesh_name = o . to_mesh ( ) . name
mesh_name = o . to_mesh ( ) . name
# find the image object in the material of the object
img_obj = None
img_obj = None
assert len ( o . material_slots ) == 1 , f " Don ' t know which material slot to pick from in mesh { mesh_name } object { o . name } "
assert len ( o . material_slots ) == 1 , f " Don ' t know which material slot to pick from in mesh { mesh_name } object { o . name } , there must only be one material slot on every mesh "
mat = o . material_slots [ 0 ]
mat = o . material_slots [ 0 ]
for node in mat . material . node_tree . nodes :
for node in mat . material . node_tree . nodes :
if node . type == " TEX_IMAGE " :
if node . type == " TEX_IMAGE " :
assert img_obj == None , f " Material on object { o . name } has more than one image node in its material, so I don ' t know which image node to use in the game engine. Make sure materials only use one image node "
img_obj = node . image
img_obj = node . image
break
assert img_obj , f " Mesh { mesh_name } in its material doesn ' t have an image object "
assert img_obj , f " Mesh { mesh_name } in its material doesn ' t have an image object "
image_filename = f " { img_obj . name } .png "
image_filename = f " { img_obj . name } .png "
if image_filename in saved_images :
if image_filename in saved_images :
pass
pass
else :
else :
save_to = f " // { EXPORT_DIRECTORY } / { image_filename } "
save_to = f " // { EXPORT_DIRECTORY } / { image_filename } "
print ( f " Saving image { image_filename } to { bpy . path . abspath ( ( save_to ) ) } ... " )
if img_obj . packed_file :
if img_obj . packed_file :
img_obj . save ( filepath = bpy . path . abspath ( save_to ) )
img_obj . save ( filepath = bpy . path . abspath ( save_to ) )
else :
else :
@ -123,272 +116,339 @@ def ensure_tex_saved_and_get_name(o) -> str:
return image_filename
return image_filename
def export_armatures ( ) :
def object_in_collection ( o , collection ) :
print ( " Exporting armatures... " )
""" Probably only works for root level collections """
exported = D . collections . get ( " Exported " )
return o . users_collection [ 0 ] . name == collection or ( o . users_collection [ 0 ] in D . collections [ collection ] . children_recursive )
assert exported != None , f " No exported collection named ' Exported ' in scene, very bad! Did everything to get exported get deleted? "
# meshes can either be Meshes, or Armatures. Armatures contain all mesh data to draw it, and any anims it has
armatures_collection = exported . children . get ( " Armatures " )
for o in D . objects :
assert armatures_collection != None , f " No child named ' Armatures ' on the exported collection, this is required "
if o . hide_get ( ) : continue
if o . type == " MESH " :
A = armatures_collection
if o . parent and o . parent . type == " ARMATURE " :
armatures_to_export = [ ]
mesh_object = o
for o in A . objects :
o = o . parent
if o . type == " MESH " :
object_transform_info = ( mesh_name , mapping @ o . location , o . rotation_euler , o . scale )
assert o . parent , f " Mesh named { o . name } without parent in armatures collection is invalid, only armatures allowed in there! "
if object_in_collection ( o , " Level " ) or object_in_collection ( o , " PlacedEntities " ) :
assert o . parent . type == " ARMATURE " , f " Mesh named { o . name } isn ' t an armature, and isn ' t a child of an armature. This isn ' t allowed. "
assert False , " Cannot put armatures in the level. The level is for static placed meshes. For dynamic entities, you put them outside of the level collection, their entity kind is encoded, and the game code decides how to draw them "
elif o . type == " ARMATURE " :
else :
armatures_to_export . append ( o )
pass
else :
#placed_entities.append((mesh_object.name,) + object_transform_info)
assert False , f " Object named { o . name } is of an unknown type ' { o . type } ' , only objects that are Meshes or Armature are allowed in the armatures collection "
armature_name = o . data . name
output_filepath = bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { armature_name } .bin " )
for armature_object in armatures_to_export :
image_filename = ensure_tex_saved_and_get_name ( mesh_object )
# get the body mesh object
print ( f " Exporting armature with image filename { image_filename } to { output_filepath } " )
body_object = None
with open ( output_filepath , " wb " ) as f :
for c in armature_object . children :
write_b8 ( f , True )
if c . name . startswith ( " Body " ) :
write_string ( f , image_filename )
assert body_object == None , f " On object { armature_object . name } , more than one child has a name that starts with ' Body ' , specifically ' { body_object . name } ' and ' { c . name } ' , only one child ' s name is allowed to start with ' Body ' in this object. "
bones_in_armature = [ ]
body_object = c
for b in o . data . bones :
bones_in_armature . append ( b )
# initialize important variables
mesh_object = body_object
# the inverse model space pos of the bones
armature_object = armature_object
write_u64 ( f , len ( bones_in_armature ) )
output_filepath = bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { armature_object . name } .bin " )
for b in bones_in_armature :
mesh_image_filename = ensure_tex_saved_and_get_name ( mesh_object )
model_space_pose = mapping @ b . matrix_local
#print(f"Exporting armature with image filename {mesh_image_filename} to {output_filepath}")
inverse_model_space_pose = ( mapping @ b . matrix_local ) . inverted ( )
with open ( output_filepath , " wb " ) as f :
parent_index = - 1
write_b8 ( f , True ) # first byte is true if it's an armature file
if b . parent :
write_string ( f , mesh_image_filename )
for i in range ( len ( bones_in_armature ) ) :
bones_in_armature = [ ]
if bones_in_armature [ i ] == b . parent :
for b in armature_object . data . bones :
parent_index = i
bones_in_armature . append ( b )
break
if parent_index == - 1 :
# the inverse model space pos of the bones
assert False , f " Couldn ' t find parent of bone { b } "
write_u64 ( f , len ( bones_in_armature ) )
#print(f"Parent of bone {b.name} is index {parent_index} in list {bones_in_armature}")
for b in bones_in_armature :
model_space_pose = mapping @ b . matrix_local
write_i32 ( f , parent_index )
inverse_model_space_pose = ( mapping @ b . matrix_local ) . inverted ( )
write_4x4matrix ( f , model_space_pose )
write_4x4matrix ( f , inverse_model_space_pose )
parent_index = - 1
write_f32 ( f , b . length )
if b . parent :
for i in range ( len ( bones_in_armature ) ) :
# write the pose information
if bones_in_armature [ i ] == b . parent :
parent_index = i
# it's very important that the array of pose bones contains the same amount of bones
break
# as there are in the edit bones. Because the edit bones are exported, etc etc. Cowabunga!
if parent_index == - 1 :
assert ( len ( o . pose . bones ) == len ( bones_in_armature ) )
assert False , f " Couldn ' t find parent of bone { b } "
#print(f"Parent of bone {b.name} is index {parent_index} in list {bones_in_armature}")
armature = o
anims = [ ]
write_i32 ( f , parent_index )
assert armature . animation_data , " Armatures are assumed to have an animation right now "
write_4x4matrix ( f , model_space_pose )
for track in armature . animation_data . nla_tracks :
write_4x4matrix ( f , inverse_model_space_pose )
for strip in track . strips :
write_f32 ( f , b . length )
anims . append ( strip . action )
print ( f " Writing { len ( anims ) } animations " )
# write the pose information
write_u64 ( f , len ( anims ) )
for animation in anims :
# it's very important that the array of pose bones contains the same amount of bones
write_string ( f , animation . name )
# as there are in the edit bones. Because the edit bones are exported, etc etc. Cowabunga!
assert ( len ( armature_object . pose . bones ) == len ( bones_in_armature ) )
old_action = armature . animation_data . action
armature . animation_data . action = animation
armature = armature_object
startFrame = int ( animation . frame_range . x )
anims = [ ]
endFrame = int ( animation . frame_range . y )
assert armature . animation_data , " Armatures are assumed to have an animation right now "
total_frames = ( endFrame - startFrame ) + 1 # the end frame is inclusive
for track in armature . animation_data . nla_tracks :
print ( f " Exporting animation { animation . name } with { total_frames } frames " )
for strip in track . strips :
anims . append ( strip . action )
write_u64 ( f , total_frames )
#print(f"Writing {len(anims)} animations")
write_u64 ( f , len ( anims ) )
time_per_anim_frame = 1.0 / float ( bpy . context . scene . render . fps )
for animation in anims :
for frame in range ( startFrame , endFrame + 1 ) :
write_string ( f , animation . name )
time_through_this_frame_occurs_at = ( frame - startFrame ) * time_per_anim_frame
bpy . context . scene . frame_set ( frame )
old_action = armature . animation_data . action
armature . animation_data . action = animation
write_f32 ( f , time_through_this_frame_occurs_at )
startFrame = int ( animation . frame_range . x )
for pose_bone_i in range ( len ( o . pose . bones ) ) :
endFrame = int ( animation . frame_range . y )
pose_bone = o . pose . bones [ pose_bone_i ]
total_frames = ( endFrame - startFrame ) + 1 # the end frame is inclusive
#print(f"Exporting animation {animation.name} with {total_frames} frames")
# in the engine, it's assumed that the poses are in the same order as the bones
# they're referring to. This checks that that is the case.
write_u64 ( f , total_frames )
assert ( pose_bone . bone == bones_in_armature [ pose_bone_i ] )
time_per_anim_frame = 1.0 / float ( bpy . context . scene . render . fps )
parent_space_pose = None
for frame in range ( startFrame , endFrame + 1 ) :
time_through_this_frame_occurs_at = ( frame - startFrame ) * time_per_anim_frame
if pose_bone . parent :
bpy . context . scene . frame_set ( frame )
parent_space_pose = pose_bone . parent . matrix . inverted ( ) @ pose_bone . matrix
else :
write_f32 ( f , time_through_this_frame_occurs_at )
parent_space_pose = mapping @ pose_bone . matrix
for pose_bone_i in range ( len ( armature_object . pose . bones ) ) :
#parent_space_pose = pose_bone.matrix
pose_bone = armature_object . pose . bones [ pose_bone_i ]
#print("parent_space_pose of the bone with no parent:")
#print(parent_space_pose)
# in the engine, it's assumed that the poses are in the same order as the bones
# they're referring to. This checks that that is the case.
#parent_space_pose = mapping @ pose_bone.matrix
assert ( pose_bone . bone == bones_in_armature [ pose_bone_i ] )
translation = parent_space_pose . to_translation ( )
rotation = parent_space_pose . to_quaternion ( )
parent_space_pose = None
scale = parent_space_pose . to_scale ( )
if pose_bone . parent :
write_v3 ( f , translation )
parent_space_pose = pose_bone . parent . matrix . inverted ( ) @ pose_bone . matrix
write_quat ( f , rotation )
else :
write_v3 ( f , scale )
parent_space_pose = mapping @ pose_bone . matrix
armature . animation_data . action = old_action
translation = parent_space_pose . to_translation ( )
# write the mesh data for the armature
rotation = parent_space_pose . to_quaternion ( )
bm = bmesh . new ( )
scale = parent_space_pose . to_scale ( )
mesh = mesh_object . to_mesh ( )
bm . from_mesh ( mesh )
write_v3 ( f , translation )
bmesh . ops . triangulate ( bm , faces = bm . faces )
write_quat ( f , rotation )
bm . transform ( mapping )
write_v3 ( f , scale )
bm . to_mesh ( mesh )
armature . animation_data . action = old_action
# write the mesh data for the armature
vertices = [ ]
bm = bmesh . new ( )
armature = o
mesh = mesh_object . to_mesh ( )
for polygon in mesh . polygons :
bm . from_mesh ( mesh )
if len ( polygon . loop_indices ) == 3 :
bmesh . ops . triangulate ( bm , faces = bm . faces )
for loopIndex in polygon . loop_indices :
bm . transform ( mapping )
loop = mesh . loops [ loopIndex ]
bm . to_mesh ( mesh )
position = mesh . vertices [ loop . vertex_index ] . undeformed_co
uv = mesh . uv_layers . active . data [ loop . index ] . uv
normal = loop . normal
vertices = [ ]
armature = armature_object
jointIndices = [ 0 , 0 , 0 , 0 ]
for polygon in mesh . polygons :
jointWeights = [ 0 , 0 , 0 , 0 ]
if len ( polygon . loop_indices ) == 3 :
for jointBindingIndex , group in enumerate ( mesh . vertices [ loop . vertex_index ] . groups ) :
for loopIndex in polygon . loop_indices :
if jointBindingIndex < 4 :
loop = mesh . loops [ loopIndex ]
groupIndex = group . group
position = mesh . vertices [ loop . vertex_index ] . undeformed_co
boneName = mesh_object . vertex_groups [ groupIndex ] . name
uv = mesh . uv_layers . active . data [ loop . index ] . uv
jointIndices [ jointBindingIndex ] = armature . data . bones . find ( boneName )
normal = loop . normal
if jointIndices [ jointBindingIndex ] == - 1 :
# it's fine that this references a real bone, the bone at index 0,
jointIndices = [ 0 , 0 , 0 , 0 ]
# because the weight of its influence is 0
jointWeights = [ 0 , 0 , 0 , 0 ]
jointIndices [ jointBindingIndex ] = 0
for jointBindingIndex , group in enumerate ( mesh . vertices [ loop . vertex_index ] . groups ) :
jointWeights [ jointBindingIndex ] = 0.0
if jointBindingIndex < 4 :
else :
groupIndex = group . group
jointWeights [ jointBindingIndex ] = group . weight
boneName = mesh_object . vertex_groups [ groupIndex ] . name
jointIndices [ jointBindingIndex ] = armature . data . bones . find ( boneName )
if jointIndices [ jointBindingIndex ] == - 1 :
vertices . append ( ( position , uv , jointIndices , normalize_joint_weights ( jointWeights ) ) )
# it's fine that this references a real bone, the bone at index 0,
# because the weight of its influence is 0
write_u64 ( f , len ( vertices ) )
jointIndices [ jointBindingIndex ] = 0
vertex_i = 0
jointWeights [ jointBindingIndex ] = 0.0
for v_and_uv in vertices :
else :
v , uv , jointIndices , jointWeights = v_and_uv
jointWeights [ jointBindingIndex ] = group . weight
write_f32 ( f , v . x )
write_f32 ( f , v . y )
write_f32 ( f , v . z )
vertices . append ( ( position , uv , jointIndices , normalize_joint_weights ( jointWeights ) ) )
write_f32 ( f , uv . x )
write_f32 ( f , uv . y )
write_u64 ( f , len ( vertices ) )
for i in range ( 4 ) :
vertex_i = 0
write_u16 ( f , jointIndices [ i ] )
for v_and_uv in vertices :
for i in range ( 4 ) :
v , uv , jointIndices , jointWeights = v_and_uv
write_f32 ( f , jointWeights [ i ] )
write_f32 ( f , v . x )
vertex_i + = 1
write_f32 ( f , v . y )
print ( f " Wrote { len ( vertices ) } vertices " )
write_f32 ( f , v . z )
write_f32 ( f , uv . x )
else : # if the parent type isn't an armature, i.e just a bog standard mesh
write_f32 ( f , uv . y )
mesh_name = o . to_mesh ( ) . name # use this over o.name so instanced objects which refer to the same mesh, both use the same serialized mesh.
for i in range ( 4 ) :
write_u16 ( f , jointIndices [ i ] )
object_transform_info = ( mesh_name , mapping @ o . location , o . rotation_euler , o . scale )
for i in range ( 4 ) :
write_f32 ( f , jointWeights [ i ] )
if object_in_collection ( o , " Level " ) and mesh_name == " CollisionCube " :
vertex_i + = 1
collision_cubes . append ( ( o . location , o . dimensions ) )
#print(f"Wrote {len(vertices)} vertices")
else :
print ( " Success! " )
if object_in_collection ( o , " Level " ) :
print ( f " Object { o . name } has mesh name { o . to_mesh ( ) . name } " )
def collect_and_validate_mesh_objects ( collection_with_only_mesh_objects ) :
assert ( o . rotation_euler . order == ' XYZ ' )
to_return = [ ]
level_object_data . append ( object_transform_info )
elif object_in_collection ( o , " PlacedEntities " ) :
for o in collection_with_only_mesh_objects . all_objects :
placed_entities . append ( ( o . name , ) + object_transform_info )
assert o . type == " MESH " , f " The collection ' { collection_with_only_mesh_objects . name } ' is assumed to contain only mesh objects but the object ' { o . name } ' which is a child of it isn ' t a mesh object "
to_return . append ( o )
if mesh_name in saved_meshes :
continue
return to_return
saved_meshes . add ( mesh_name )
print ( f """ Mesh name { mesh_name } in level { object_in_collection ( o , " Level " ) } collections { o . users_collection } """ )
def get_startswith ( name_of_overarching_thing , iterable , starts_with ) :
image_filename = ensure_tex_saved_and_get_name ( o )
"""
Gets the thing in iterable that starts with starts with , and makes sure there ' s only *one* thing that starts with starts with
assert ( mesh_name != LEVEL_EXPORT_NAME )
name_of_overarching_thing is for the error message , for reporting in what collection things went wrong in .
output_filepath = bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { mesh_name } .bin " )
"""
print ( f " Exporting mesh to { output_filepath } with image_filename { image_filename } " )
to_return = None
with open ( output_filepath , " wb " ) as f :
for thing in iterable :
write_b8 ( f , False ) # if it's an armature or not, first byte of the file
if thing . name . startswith ( starts_with ) :
write_string ( f , image_filename ) # the image filename!
assert to_return == None , f " Duplicate thing that starts with ' { starts_with } ' found in { name_of_overarching_thing } called { thing . name } "
to_return = thing
bm = bmesh . new ( )
assert to_return != None , f " Couldn ' t find thing that starts with ' { starts_with } ' as a child of ' { name_of_overarching_thing } ' , but one is required "
mesh = o . to_mesh ( )
return to_return
bm . from_mesh ( mesh )
bmesh . ops . triangulate ( bm , faces = bm . faces )
def no_hidden ( objects ) :
bm . transform ( mapping )
to_return = [ ]
bm . to_mesh ( mesh )
for o in objects :
if not o . hide_get ( ) :
to_return . append ( o )
vertices = [ ]
return to_return
for polygon in mesh . polygons :
def export_meshes_and_levels ( ) :
if len ( polygon . loop_indices ) == 3 :
print ( " Exporting meshes and levels " )
for loopIndex in polygon . loop_indices :
exported = D . collections . get ( " Exported " )
loop = mesh . loops [ loopIndex ]
assert exported != None , f " No exported collection named ' Exported ' in scene, very bad! Did everything to get exported get deleted? "
position = mesh . vertices [ loop . vertex_index ] . undeformed_co
uv = mesh . uv_layers . active . data [ loop . index ] . uv
mesh_names_to_export = set ( )
normal = loop . normal
meshes_to_export = [ ]
vertices . append ( ( position , uv ) )
# add the collection 'Meshes' objects to mesh_objects_to_export
if True :
write_u64 ( f , len ( vertices ) )
meshes = get_startswith ( " Exported " , exported . children , " Meshes " )
print ( f " \n \n { output_filepath } vertices: " )
to_export = collect_and_validate_mesh_objects ( meshes )
for v_and_uv in vertices :
for m in no_hidden ( to_export ) :
v , uv = v_and_uv
mesh_names_to_export . add ( m . name )
write_f32 ( f , v . x )
meshes_to_export . append ( m )
write_f32 ( f , v . y )
write_f32 ( f , v . z )
# export each level: its placed entities, placed meshes, and add to meshes_to_export and mesh_names_to_export. Those must be exported to their correct paths for the rooms to point at valid data.
write_f32 ( f , uv . x )
rooms = exported . children . get ( " Rooms " )
write_f32 ( f , uv . y )
assert rooms != None , f " No child named ' Rooms ' on the exported collection, this is required "
if len ( vertices ) < 100 :
with open ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { ROOMS_EXPORT_NAME } .bin " ) , " wb " ) as f :
print ( v )
write_u64 ( f , len ( rooms . children ) )
print ( f " Wrote { len ( vertices ) } vertices " )
for room_collection in rooms . children :
write_string ( f , room_collection . name ) # the name of the room is the name of the room collection
with open ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { LEVEL_EXPORT_NAME } .bin " ) , " wb " ) as f :
write_u64 ( f , len ( level_object_data ) )
# placed meshes (exported mesh name (which is the object's name), location, rotation, scale)
for o in level_object_data :
placed_meshes = [ ]
mesh_name , blender_pos , blender_rotation , blender_scale = o
if True :
print ( f " Writing instanced object of mesh { mesh_name } " )
meshes_collection = get_startswith ( room_collection . name , room_collection . children , " Meshes " )
write_string ( f , mesh_name )
for m in no_hidden ( meshes_collection . objects ) :
write_f32 ( f , blender_pos . x )
assert m . rotation_euler . order == ' XYZ ' , f " Invalid rotation euler order for object of name ' { m . name } ' , it ' s { m . rotation_euler . order } but must be XYZ "
write_f32 ( f , blender_pos . y )
assert m . type == " MESH " , f " In meshes collection ' { meshes_collection . name } ' object { m . name } must be of type ' MESH ' but instead is of type { m . type } "
write_f32 ( f , blender_pos . z )
if not m . name in mesh_names_to_export :
mesh_names_to_export . add ( m . name )
write_f32 ( f , blender_rotation . x )
meshes_to_export . append ( m )
write_f32 ( f , blender_rotation . y )
placed_meshes . append ( ( m . name , mapping @ m . location , m . rotation_euler , m . scale ) )
write_f32 ( f , blender_rotation . z )
# colliders (location, dimensions)
write_f32 ( f , blender_scale . x )
placed_colliders = [ ]
write_f32 ( f , blender_scale . y )
if True :
write_f32 ( f , blender_scale . z )
colliders_collection = get_startswith ( room_collection . name , room_collection . children , " Colliders " )
for o in no_hidden ( colliders_collection . objects ) :
write_u64 ( f , len ( collision_cubes ) )
assert o . name . startswith ( " CollisionCube " ) , f " Object { o . name } doesn ' t start with ' CollisionCube ' and it ' s in the Colliders group of room { room_collection . name } , colliders must be collision cubes "
for c in collision_cubes :
placed_colliders . append ( ( o . location , o . dimensions ) )
blender_pos , blender_dims = c
write_f32 ( f , blender_pos . x )
# fill out placed_entities with a tuple of (name, location, rotation, scale)
write_f32 ( f , - blender_pos . y )
placed_entities = [ ]
write_f32 ( f , blender_dims . x )
if True :
write_f32 ( f , blender_dims . y )
entities_collection = get_startswith ( room_collection . name , room_collection . children , " Entities " )
assert ( blender_dims . x > 0.0 )
assert ( blender_dims . y > 0.0 )
for o in no_hidden ( entities_collection . objects ) :
assert o . rotation_euler . order == ' XYZ ' , f " Invalid rotation euler order for object of name ' { o . name } ' , it ' s { o . rotation_euler . order } but must be XYZ "
write_u64 ( f , len ( placed_entities ) )
placed_entities . append ( ( o . name , mapping @ o . location , o . rotation_euler , o . scale ) )
for p in placed_entities :
# underscore is mesh name, prefer object name for name of npc. More obvious
object_name , _ , blender_pos , blender_rotation , blender_scale = p
write_u64 ( f , len ( placed_meshes ) )
print ( f " Writing placed entity ' { object_name } ' " )
for p in placed_meshes :
write_string ( f , object_name )
mesh_name , blender_pos , blender_rotation , blender_scale = p
write_f32 ( f , blender_pos . x )
write_string ( f , mesh_name )
write_f32 ( f , blender_pos . y )
write_f32 ( f , blender_pos . x )
write_f32 ( f , blender_pos . z )
write_f32 ( f , blender_pos . y )
write_f32 ( f , blender_pos . z )
write_f32 ( f , blender_rotation . x )
write_f32 ( f , blender_rotation . x )
write_f32 ( f , blender_rotation . y )
write_f32 ( f , blender_rotation . y )
write_f32 ( f , blender_rotation . z )
write_f32 ( f , blender_rotation . z )
write_f32 ( f , blender_scale . x )
write_f32 ( f , blender_scale . x )
write_f32 ( f , blender_scale . y )
write_f32 ( f , blender_scale . y )
write_f32 ( f , blender_scale . z )
write_f32 ( f , blender_scale . z )
write_u64 ( f , len ( placed_colliders ) )
for c in placed_colliders :
blender_pos , blender_dims = c
write_f32 ( f , blender_pos . x )
write_f32 ( f , - blender_pos . y )
write_f32 ( f , blender_dims . x )
write_f32 ( f , blender_dims . y )
assert blender_dims . x > 0.0
assert blender_dims . y > 0.0
write_u64 ( f , len ( placed_entities ) )
for e in placed_entities :
entity_name , blender_pos , blender_rotation , blender_scale = e
write_string ( f , entity_name )
write_f32 ( f , blender_pos . x )
write_f32 ( f , blender_pos . y )
write_f32 ( f , blender_pos . z )
write_f32 ( f , blender_rotation . x )
write_f32 ( f , blender_rotation . y )
write_f32 ( f , blender_rotation . z )
write_f32 ( f , blender_scale . x )
write_f32 ( f , blender_scale . y )
write_f32 ( f , blender_scale . z )
# export all the meshes that the rooms file is referring to, and all the meshes that just need to be plain exported
for m in meshes_to_export :
assert o . type == " MESH "
assert o . parent == None , f " Mesh ' { m . name } ' has a parent, but exporting mesh objects with parents isn ' t supported "
mesh_name = m . name
image_filename = ensure_tex_saved_and_get_name ( m )
output_filepath = bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { mesh_name } .bin " )
with open ( output_filepath , " wb " ) as f :
write_b8 ( f , False ) # if it's an armature or not, first byte of the file
write_string ( f , image_filename )
bm = bmesh . new ( )
mesh = m . to_mesh ( )
assert len ( mesh . uv_layers ) == 1 , f " Mesh object { m . name } has more than 1 uv layer, which isn ' t allowed "
bm . from_mesh ( mesh )
bmesh . ops . triangulate ( bm , faces = bm . faces )
bm . transform ( mapping )
bm . to_mesh ( mesh )
vertices = [ ]
for polygon in mesh . polygons :
if len ( polygon . loop_indices ) == 3 :
for loopIndex in polygon . loop_indices :
loop = mesh . loops [ loopIndex ]
position = mesh . vertices [ loop . vertex_index ] . undeformed_co
uv = mesh . uv_layers . active . data [ loop . index ] . uv
normal = loop . normal
vertices . append ( ( position , uv ) )
write_u64 ( f , len ( vertices ) )
for v_and_uv in vertices :
v , uv = v_and_uv
write_f32 ( f , v . x )
write_f32 ( f , v . y )
write_f32 ( f , v . z )
write_f32 ( f , uv . x )
write_f32 ( f , uv . y )
print ( " Success! " )
export_armatures ( )
export_meshes_and_levels ( )