2018-05-11 02:43:57 +09:00
bl_info = {
2018-05-11 04:24:47 +09:00
" name " : " Export COL for Super Mario Sunshine " ,
2018-05-11 02:43:57 +09:00
" author " : " Blank " ,
" version " : ( 1 , 0 , 0 ) ,
" blender " : ( 2 , 71 , 0 ) ,
" location " : " File > Export > Collision (.col) " ,
2018-05-11 04:24:47 +09:00
" description " : " This script allows you do export col files directly from blender. Based on Blank ' s obj2col " ,
2018-05-16 05:42:13 +09:00
" warning " : " Runs update function every 0.2 seconds " ,
2018-05-11 02:43:57 +09:00
" category " : " Import-Export "
}
2018-05-19 04:34:51 +09:00
import random
2018-05-11 02:43:57 +09:00
import bpy
2018-05-11 04:24:47 +09:00
import bmesh
2018-05-16 05:42:13 +09:00
import threading
2018-05-14 01:59:54 +09:00
from enum import Enum
2018-05-11 04:24:47 +09:00
from btypes . big_endian import *
2018-05-12 08:32:51 +09:00
from bpy . types import PropertyGroup , Panel , Scene , Operator
from bpy . utils import register_class , unregister_class
2018-05-16 05:42:13 +09:00
from bpy . app . handlers import persistent
2018-05-11 02:43:57 +09:00
from bpy_extras . io_utils import ExportHelper
from bpy . props import ( BoolProperty ,
FloatProperty ,
StringProperty ,
2018-05-12 08:32:51 +09:00
EnumProperty ,
IntProperty ,
PointerProperty ,
2018-05-11 02:43:57 +09:00
)
2018-07-25 06:39:43 +09:00
2018-05-16 05:42:13 +09:00
2018-05-11 02:43:57 +09:00
class Header ( Struct ) :
vertex_count = uint32
vertex_offset = uint32
group_count = uint32
group_offset = uint32
class Vertex ( Struct ) :
x = float32
y = float32
z = float32
def __init__ ( self , x , y , z ) :
self . x = x
self . y = y
self . z = z
class Group ( Struct ) :
2018-05-19 04:34:51 +09:00
CollisionType = uint16 #Properties of collision. e.g. is it water? or what?
2018-05-11 02:43:57 +09:00
triangle_count = uint16
2018-05-19 04:34:51 +09:00
__padding__ = Padding ( 1 , b ' \x00 ' ) #Group flags, set them to 0 here
has_ColParameter = bool8 #Set 0x0001 to 1 if we have ColParameter values so the game doesn't ignore it
__padding__ = Padding ( 2 ) #Actual padding
2018-05-11 02:43:57 +09:00
vertex_index_offset = uint32
2018-05-19 04:34:51 +09:00
TerrainType_offset = uint32 # 0-18,20,21,23,24,27-31
unknown_offset = uint32 # 0-27
ColParameter_offset = uint32 # 0,1,2,3,4,8,255,6000,7500,7800,8000,8400,9000,10000,10300,12000,14000,17000,19000,20000,21000,22000,27500,30300
2018-05-11 02:43:57 +09:00
class Triangle :
def __init__ ( self ) :
self . vertex_indices = None
2018-05-19 04:34:51 +09:00
self . ColType = 0
self . TerrainType = 0
self . unknown = 0
2018-08-13 01:54:10 +09:00
self . ColParameter = None
2018-05-11 02:43:57 +09:00
@property
2018-05-19 04:34:51 +09:00
def has_ColParameter ( self ) :
return self . ColParameter is not None
2018-05-11 02:43:57 +09:00
2018-07-25 06:39:43 +09:00
2018-05-11 02:43:57 +09:00
def pack ( stream , vertices , triangles ) : #pack triangles into col file
groups = [ ]
for triangle in triangles :
for group in groups : #for each triangle add to appropriate group
2018-05-19 04:34:51 +09:00
if triangle . ColType != group . CollisionType : continue #break out of loop to next cycle
2018-05-11 02:43:57 +09:00
group . triangles . append ( triangle )
break
else : #if no group has been found
group = Group ( ) #create a new group
2018-05-19 04:34:51 +09:00
group . CollisionType = triangle . ColType
group . has_ColParameter = triangle . has_ColParameter
2018-05-11 02:43:57 +09:00
group . triangles = [ triangle ]
groups . append ( group ) #add to list of groups
header = Header ( )
header . vertex_count = len ( vertices )
header . vertex_offset = Header . sizeof ( ) + Group . sizeof ( ) * len ( groups )
header . group_count = len ( groups )
header . group_offset = Header . sizeof ( )
Header . pack ( stream , header )
stream . write ( b ' \x00 ' * Group . sizeof ( ) * len ( groups ) )
for vertex in vertices :
Vertex . pack ( stream , vertex )
for group in groups :
group . triangle_count = len ( group . triangles )
group . vertex_index_offset = stream . tell ( )
for triangle in group . triangles :
uint16 . pack ( stream , triangle . vertex_indices [ 0 ] )
uint16 . pack ( stream , triangle . vertex_indices [ 1 ] )
uint16 . pack ( stream , triangle . vertex_indices [ 2 ] )
for group in groups :
2018-05-19 04:34:51 +09:00
group . TerrainType_offset = stream . tell ( )
2018-05-11 02:43:57 +09:00
for triangle in group . triangles :
2018-05-19 04:34:51 +09:00
uint8 . pack ( stream , triangle . TerrainType )
2018-05-11 02:43:57 +09:00
for group in groups :
2018-05-19 04:34:51 +09:00
group . unknown_offset = stream . tell ( )
2018-05-11 02:43:57 +09:00
for triangle in group . triangles :
2018-05-19 04:34:51 +09:00
uint8 . pack ( stream , triangle . unknown )
2018-05-11 02:43:57 +09:00
for group in groups :
2018-05-19 04:34:51 +09:00
if not group . has_ColParameter :
group . ColParameter_offset = 0
2018-05-11 02:43:57 +09:00
else :
2018-05-19 04:34:51 +09:00
group . ColParameter_offset = stream . tell ( )
2018-05-11 02:43:57 +09:00
for triangle in group . triangles :
2018-08-13 01:54:10 +09:00
if triangle . ColParameter is not None :
uint16 . pack ( stream , triangle . ColParameter )
else :
uint16 . pack ( stream , 0 )
2018-05-11 02:43:57 +09:00
stream . seek ( header . group_offset )
for group in groups :
Group . pack ( stream , group )
def unpack ( stream ) :
header = Header . unpack ( stream )
stream . seek ( header . group_offset )
groups = [ Group . unpack ( stream ) for _ in range ( header . group_count ) ]
stream . seek ( header . vertex_offset )
vertices = [ Vertex . unpack ( stream ) for _ in range ( header . vertex_count ) ]
for group in groups :
group . triangles = [ Triangle ( ) for _ in range ( group . triangle_count ) ]
for triangle in group . triangles :
2018-05-19 04:34:51 +09:00
triangle . ColType = group . CollisionType
2018-05-11 02:43:57 +09:00
for group in groups :
stream . seek ( group . vertex_index_offset )
for triangle in group . triangles :
triangle . vertex_indices = [ uint16 . unpack ( stream ) for _ in range ( 3 ) ]
for group in groups :
2018-05-19 04:34:51 +09:00
stream . seek ( group . TerrainType_offset )
2018-05-11 02:43:57 +09:00
for triangle in group . triangles :
2018-05-19 04:34:51 +09:00
triangle . TerrainType = uint8 . unpack ( stream )
2018-05-11 02:43:57 +09:00
for group in groups :
2018-05-19 04:34:51 +09:00
stream . seek ( group . unknown_offset )
2018-05-11 02:43:57 +09:00
for triangle in group . triangles :
2018-05-19 04:34:51 +09:00
triangle . unknown = uint8 . unpack ( stream )
2018-05-11 02:43:57 +09:00
for group in groups :
2018-05-19 04:34:51 +09:00
if not group . has_ColParameter : continue
stream . seek ( group . ColParameter_offset )
2018-05-11 02:43:57 +09:00
for triangle in group . triangles :
2018-05-19 04:34:51 +09:00
triangle . ColParameter = uint16 . unpack ( stream )
2018-05-11 02:43:57 +09:00
triangles = sum ( ( group . triangles for group in groups ) , [ ] )
return vertices , triangles
2018-05-14 08:11:14 +09:00
class ImportCOL ( Operator , ExportHelper ) : #Operator that exports the collision model into .col file
""" Import a COL file """
bl_idname = " import_mesh.col "
bl_label = " Import COL "
filter_glob = StringProperty (
default = " *.col " ,
options = { ' HIDDEN ' } ,
) #This property filters what you see in the file browser to just .col files
check_extension = True
filename_ext = " .col " #This is the extension that the model will have
def execute ( self , context ) :
ColStream = open ( self . filepath , ' rb ' )
CollisionVertexList = [ ] #Store a list of verticies
Triangles = [ ] #List of triangles, each containing indicies of verticies
CollisionVertexList , Triangles = unpack ( ColStream )
2018-05-15 03:31:31 +09:00
2018-05-14 08:11:14 +09:00
mesh = bpy . data . meshes . new ( " mesh " ) # add a new mesh
2018-07-25 06:48:16 +09:00
obj = bpy . data . objects . new ( " CollisionObj " , mesh ) # add a new object using the mesh
2018-05-14 08:11:14 +09:00
scene = bpy . context . scene
scene . objects . link ( obj ) # put the object into the scene (link)
scene . objects . active = obj # set as the active object in the scene
obj . select = True # select object
2018-05-18 08:21:46 +09:00
mesh = obj . data
2018-05-14 08:11:14 +09:00
bm = bmesh . new ( )
BMeshVertexList = [ ]
2018-05-15 03:31:31 +09:00
for v in CollisionVertexList :
BMeshVertexList . append ( bm . verts . new ( ( v . x , - v . z , v . y ) ) ) # add a new vert
2018-05-18 08:21:46 +09:00
2018-05-14 08:11:14 +09:00
for f in Triangles :
2018-05-15 03:31:31 +09:00
try : #Try and catch to avoid exception on duplicate triangles. Dodgy...
MyFace = bm . faces . new ( ( BMeshVertexList [ f . vertex_indices [ 0 ] ] , BMeshVertexList [ f . vertex_indices [ 1 ] ] , BMeshVertexList [ f . vertex_indices [ 2 ] ] ) )
2018-05-18 08:21:46 +09:00
for i in range ( 0 , len ( obj . data . materials ) ) : #Scan materials to find match
mat = obj . data . materials [ i ]
2018-05-19 04:34:51 +09:00
if f . ColType == mat . ColEditor . ColType and f . TerrainType == mat . ColEditor . TerrainType and f . unknown == mat . ColEditor . UnknownField : #Equate unknowns
ColParameterAreEqual = ( f . ColParameter == mat . ColEditor . ColParameterField )
ColParameterDontExist = f . ColParameter is None and mat . ColEditor . HasColParameterField is False #If the ColParameter doesn't exist we need to check for that case
if ColParameterAreEqual or ColParameterDontExist :
2020-01-09 05:06:05 +09:00
MyFace . material_index = i
2018-05-18 08:21:46 +09:00
break #We assigned our material
else : #We did not find a material that matched
2018-05-19 04:34:51 +09:00
MaterialName = str ( f . ColType ) + " , " + str ( f . TerrainType ) + " , " + str ( f . unknown ) + " , " + str ( f . ColParameter )
2018-05-18 08:21:46 +09:00
mat = bpy . data . materials . new ( name = MaterialName )
2018-05-19 04:34:51 +09:00
random . seed ( hash ( MaterialName ) ) #Not actually random
Red = random . random ( )
Green = random . random ( )
Blue = random . random ( )
2018-05-18 08:21:46 +09:00
mat . diffuse_color = ( Red , Green , Blue )
2018-05-19 04:34:51 +09:00
mat . ColEditor . ColType = f . ColType #Set collision values
mat . ColEditor . TerrainType = f . TerrainType
mat . ColEditor . UnknownField = f . unknown
2018-05-18 08:21:46 +09:00
2018-05-19 04:34:51 +09:00
if f . ColParameter is not None :
mat . ColEditor . HasColParameterField = True
mat . ColEditor . ColParameterField = f . ColParameter
2018-05-18 08:21:46 +09:00
else :
2018-05-19 04:34:51 +09:00
mat . ColEditor . HasColParameterField = False
mat . ColEditor . ColParameterField = 0
2018-05-18 08:21:46 +09:00
obj . data . materials . append ( mat ) #add material to our object
MyFace . material_index = len ( obj . data . materials ) - 1 #Since material was just added it will be the last index
2018-05-15 03:31:31 +09:00
except :
continue
2018-05-14 08:11:14 +09:00
bm . to_mesh ( mesh )
mesh . update ( )
bm . free ( )
return { ' FINISHED ' }
2018-05-14 02:17:38 +09:00
class ExportCOL ( Operator , ExportHelper ) : #Operator that exports the collision model into .col file
2018-05-11 02:43:57 +09:00
""" Save a COL file """
bl_idname = " export_mesh.col "
bl_label = " Export COL "
2018-05-14 02:17:38 +09:00
filter_glob = StringProperty (
2018-05-11 02:43:57 +09:00
default = " *.col " ,
options = { ' HIDDEN ' } ,
2018-05-14 02:17:38 +09:00
) #This property filters what you see in the file browser to just .col files
2018-05-11 02:43:57 +09:00
check_extension = True
2018-05-14 02:17:38 +09:00
filename_ext = " .col " #This is the extension that the model will have
2018-05-11 02:43:57 +09:00
#To do: add material presets
2018-05-15 04:16:47 +09:00
2018-05-15 04:36:36 +09:00
Scale = FloatProperty (
2018-05-15 04:16:47 +09:00
name = " Scale factor " ,
description = " Scale the col file by this amount " ,
default = 1 ,
)
2018-05-11 02:43:57 +09:00
def execute ( self , context ) : # execute() is called by blender when running the operator.
2018-05-14 02:17:38 +09:00
VertexList = [ ] #Store a list of verticies
Triangles = [ ] #List of triangles, each containing indicies of verticies
2018-05-18 06:50:56 +09:00
IndexOffset = 0 #Since each object starts their vertex indicies at 0, we need to shift these indicies once we add elements to the vertex list from various objects
for Obj in bpy . context . scene . objects : #for all objects
2018-07-25 06:54:14 +09:00
bpy . ops . object . mode_set ( mode = ' OBJECT ' ) #Set mode to be object mode
2018-07-25 06:48:16 +09:00
if Obj . type != ' MESH ' :
continue
2018-05-18 06:50:56 +09:00
bm = bmesh . new ( ) #Define new bmesh
2018-05-11 04:24:47 +09:00
MyMesh = Obj . to_mesh ( context . scene , True , ' PREVIEW ' ) #make a copy of the object we can modify freely
2018-05-14 02:17:38 +09:00
bm . from_mesh ( MyMesh ) #Add the above copy into the bmesh
2018-05-18 06:50:56 +09:00
bmesh . ops . triangulate ( bm , faces = bm . faces [ : ] , quad_method = 0 , ngon_method = 0 ) #triangulate bmesh
for Vert in bm . verts :
VertexList . append ( Vertex ( Vert . co . x * self . Scale , Vert . co . z * self . Scale , - Vert . co . y * self . Scale ) ) #add in verts, make sure y is up
for Face in bm . faces :
MyTriangle = Triangle ( )
MyTriangle . vertex_indices = [ Face . verts [ 0 ] . index + IndexOffset , Face . verts [ 1 ] . index + IndexOffset , Face . verts [ 2 ] . index + IndexOffset ] #add three vertex indicies
slot = Obj . material_slots [ Face . material_index ]
mat = slot . material . ColEditor
if mat is not None :
2018-05-19 04:34:51 +09:00
MyTriangle . ColType = mat . ColType
MyTriangle . TerrainType = mat . TerrainType
MyTriangle . unknown = mat . UnknownField
if mat . HasColParameterField == True :
MyTriangle . ColParameter = mat . ColParameterField
2018-05-18 06:50:56 +09:00
Triangles . append ( MyTriangle ) #add triangles
bm . free ( )
del bm
IndexOffset = len ( VertexList ) #set offset
2018-05-14 04:24:43 +09:00
2018-05-11 02:43:57 +09:00
ColStream = open ( self . filepath , ' wb ' )
pack ( ColStream , VertexList , Triangles )
return { ' FINISHED ' } # this lets blender know the operator finished successfully.
2018-05-14 01:59:54 +09:00
2018-05-12 08:32:51 +09:00
class CollisionProperties ( PropertyGroup ) : #This defines the UI elements
2018-05-19 04:34:51 +09:00
ColType = IntProperty ( name = " Collision type " , default = 0 , min = 0 , max = 65535 ) #Here we put parameters for the UI elements and point to the Update functions
TerrainType = IntProperty ( name = " Sound " , default = 0 , min = 0 , max = 255 )
UnknownField = IntProperty ( name = " Unknown " , default = 0 , min = 0 , max = 255 ) #I probably should have made these an array
HasColParameterField = BoolProperty ( name = " Has Parameter " , default = False )
ColParameterField = IntProperty ( name = " Parameter " , default = 0 , min = 0 , max = 65535 )
2018-05-12 08:32:51 +09:00
class CollisionPanel ( Panel ) : #This panel houses the UI elements defined in the CollisionProperties
2018-05-14 01:59:54 +09:00
bl_label = " Edit Collision Values "
2018-05-11 20:38:36 +09:00
bl_space_type = " PROPERTIES "
bl_region_type = " WINDOW "
2018-05-18 06:50:56 +09:00
bl_context = " material "
2018-05-11 20:38:36 +09:00
2018-05-16 05:42:13 +09:00
@classmethod
2018-05-18 06:50:56 +09:00
def poll ( cls , context ) : #stolen from blender
mat = context . material
engine = context . scene . render . engine
return check_material ( mat ) and ( mat . type in { ' SURFACE ' , ' WIRE ' } )
2018-05-11 20:38:36 +09:00
def draw ( self , context ) :
2018-05-18 06:50:56 +09:00
mat = context . material . ColEditor
2018-05-12 08:32:51 +09:00
column1 = self . layout . column ( align = True )
2018-05-19 04:34:51 +09:00
column1 . prop ( mat , " ColType " )
column1 . prop ( mat , " TerrainType " )
column1 . prop ( mat , " UnknownField " )
2018-05-12 08:32:51 +09:00
2018-05-19 04:34:51 +09:00
column1 . prop ( mat , " HasColParameterField " )
2018-05-12 08:32:51 +09:00
column2 = self . layout . column ( align = True )
2018-05-19 04:34:51 +09:00
column2 . prop ( mat , " ColParameterField " )
column2 . enabled = mat . HasColParameterField #must have "Has ColParameter" checked
2018-07-25 06:39:43 +09:00
2018-05-26 04:17:43 +09:00
def check_material ( mat ) :
if mat is not None :
if mat . use_nodes :
if mat . active_node_material is not None :
return True
return False
return True
return False
2018-05-21 07:16:00 +09:00
2018-07-25 06:39:43 +09:00
classes = ( ExportCOL , ImportCOL , CollisionPanel , CollisionProperties ) #list of classes to register/unregister
2018-05-11 02:43:57 +09:00
def register ( ) :
2018-05-12 08:32:51 +09:00
for i in classes :
register_class ( i )
2018-05-18 06:50:56 +09:00
bpy . types . Material . ColEditor = PointerProperty ( type = CollisionProperties ) #store in the scene
2018-05-14 08:11:14 +09:00
bpy . types . INFO_MT_file_export . append ( menu_export ) #Add to export menu
2018-05-18 08:21:46 +09:00
bpy . types . INFO_MT_file_import . append ( menu_import ) #Add to import menu
2018-05-12 08:32:51 +09:00
2018-05-11 02:43:57 +09:00
2018-05-14 08:11:14 +09:00
def menu_export ( self , context ) :
2018-05-11 02:43:57 +09:00
self . layout . operator ( ExportCOL . bl_idname , text = " Collision (.col) " )
2018-05-14 08:11:14 +09:00
def menu_import ( self , context ) :
self . layout . operator ( ImportCOL . bl_idname , text = " Collision (.col) " )
2018-05-11 02:43:57 +09:00
def unregister ( ) :
2018-05-12 08:32:51 +09:00
for i in classes :
unregister_class ( i )
2018-05-14 08:11:14 +09:00
bpy . types . INFO_MT_file_export . remove ( menu_export )
2018-05-18 08:21:46 +09:00
bpy . types . INFO_MT_file_import . remove ( menu_import )
2018-05-11 02:43:57 +09:00
2018-05-16 05:42:13 +09:00
2018-05-11 02:43:57 +09:00
# This allows you to run the script directly from blenders text editor
# to test the addon without having to install it.
if __name__ == " __main__ " :
register ( )