From a2dd11fbc663e337b28fe2314fd3c64611b29e42 Mon Sep 17 00:00:00 2001 From: Augs Date: Thu, 10 May 2018 18:43:57 +0100 Subject: [PATCH] First commit --- BlenderCOL.py | 217 +++++++++++++++++++ btypes/__init__.py | 23 ++ btypes/__pycache__/__init__.cpython-34.pyc | Bin 0 -> 887 bytes btypes/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 843 bytes btypes/__pycache__/big_endian.cpython-34.pyc | Bin 0 -> 603 bytes btypes/__pycache__/big_endian.cpython-36.pyc | Bin 0 -> 539 bytes btypes/__pycache__/types.cpython-34.pyc | Bin 0 -> 9808 bytes btypes/__pycache__/types.cpython-36.pyc | Bin 0 -> 9162 bytes btypes/big_endian.py | 16 ++ btypes/types.py | 211 ++++++++++++++++++ 10 files changed, 467 insertions(+) create mode 100644 BlenderCOL.py create mode 100644 btypes/__init__.py create mode 100644 btypes/__pycache__/__init__.cpython-34.pyc create mode 100644 btypes/__pycache__/__init__.cpython-36.pyc create mode 100644 btypes/__pycache__/big_endian.cpython-34.pyc create mode 100644 btypes/__pycache__/big_endian.cpython-36.pyc create mode 100644 btypes/__pycache__/types.cpython-34.pyc create mode 100644 btypes/__pycache__/types.cpython-36.pyc create mode 100644 btypes/big_endian.py create mode 100644 btypes/types.py diff --git a/BlenderCOL.py b/BlenderCOL.py new file mode 100644 index 0000000..2b4e1cc --- /dev/null +++ b/BlenderCOL.py @@ -0,0 +1,217 @@ +bl_info = { + "name": "Export COL with Obj2Col", + "author": "Blank", + "version": (1, 0, 0), + "blender": (2, 71, 0), + "location": "File > Export > Collision (.col)", + "description": "This script allows you do export col files quickly using obj2col directly from blender", + "warning": "Might break, doing this mostly for my own convinience", + "category": "Import-Export" +} + +import bpy +import os +from bpy_extras.io_utils import ExportHelper +from bpy.props import (BoolProperty, + FloatProperty, + StringProperty, + EnumProperty, + ) + + +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): + unknown0 = uint8 # 0,1,2,4,6,7,8,64,128,129,132,135,160,192, bitfield? + unknown1 = uint8 # 0-12 + triangle_count = uint16 + __padding__ = Padding(1,b'\x00') + has_unknown4 = bool8 + __padding__ = Padding(2) + vertex_index_offset = uint32 + unknown2_offset = uint32 # 0-18,20,21,23,24,27-31 + unknown3_offset = uint32 # 0-27 + unknown4_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 + + +class Triangle: + + def __init__(self): + self.vertex_indices = None + self.unknown0 = 128 + self.unknown1 = 0 + self.unknown2 = 0 + self.unknown3 = 0 + self.unknown4 = None + + @property + def has_unknown4(self): + return self.unknown4 is not None + + +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 + if triangle.unknown0 != group.unknown0: continue #break out of loop to next cycle + if triangle.unknown1 != group.unknown1: continue + if triangle.has_unknown4 != group.has_unknown4: continue + group.triangles.append(triangle) + break + else: #if no group has been found + group = Group() #create a new group + group.unknown0 = triangle.unknown0 + group.unknown1 = triangle.unknown1 + group.has_unknown4 = triangle.has_unknown4 + 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: + group.unknown2_offset = stream.tell() + for triangle in group.triangles: + uint8.pack(stream,triangle.unknown2) + + for group in groups: + group.unknown3_offset = stream.tell() + for triangle in group.triangles: + uint8.pack(stream,triangle.unknown3) + + for group in groups: + if not group.has_unknown4: + group.unknown4_offset = 0 + else: + group.unknown4_offset = stream.tell() + for triangle in group.triangles: + uint16.pack(stream,triangle.unknown4) + + 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: + triangle.unknown0 = group.unknown0 + triangle.unknown1 = group.unknown1 + + 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: + stream.seek(group.unknown2_offset) + for triangle in group.triangles: + triangle.unknown2 = uint8.unpack(stream) + + for group in groups: + stream.seek(group.unknown3_offset) + for triangle in group.triangles: + triangle.unknown3 = uint8.unpack(stream) + + for group in groups: + if not group.has_unknown4: continue + stream.seek(group.unknown4_offset) + for triangle in group.triangles: + triangle.unknown4 = uint16.unpack(stream) + + triangles = sum((group.triangles for group in groups),[]) + + return vertices,triangles + +class ExportCOL(bpy.types.Operator, ExportHelper): + """Save a COL file""" + bl_idname = "export_mesh.col" + bl_label = "Export COL" + filter_glob = StringProperty( + default="*.col", + options={'HIDDEN'}, + ) + + check_extension = True + filename_ext = ".col" + + #To do: add material presets + + def execute(self, context): # execute() is called by blender when running the operator. + Obj = bpy.context.scene.objects.active + Mesh = Obj.data + VertexList = [] + Triangles = [] + for Vert in Mesh.vertices: + VertexList.append(Vertex(Vert.co.x,Vert.co.y,Vert.co.z)) #add in verts + + for Face in Mesh.polygons: + MyTriangle = Triangle() + MyTriangle.vertex_indices = [Face.vertices[0],Face.vertices[1],Face.vertices[2]] #add three vertex indicies + Triangles.append(MyTriangle) #add triangles + + ColStream = open(self.filepath,'wb') + pack(ColStream,VertexList,Triangles) + return {'FINISHED'} # this lets blender know the operator finished successfully. + + + +def register(): + bpy.utils.register_class(ExportCOL) + bpy.types.INFO_MT_file_export.append(menu_func) + +def menu_func(self, context): + self.layout.operator(ExportCOL.bl_idname, text="Collision (.col)") + +def unregister(): + bpy.utils.unregister_class(ExportCOL) + bpy.types.INFO_MT_file_export.remove(menu_func) + + +# 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() \ No newline at end of file diff --git a/btypes/__init__.py b/btypes/__init__.py new file mode 100644 index 0000000..af9cfc2 --- /dev/null +++ b/btypes/__init__.py @@ -0,0 +1,23 @@ +"""Module for reading and writing data structures.""" + +from btypes.types import * + + +class FormatError(Exception): pass + + +def align(stream,length,padding=b'This is padding data to alignment.'): + if stream.tell() % length == 0: return + n,r = divmod(length - (stream.tell() % length),len(padding)) + stream.write(n*padding + padding[0:r]) + + +SEEK_POS = 0 +SEEK_CUR = 1 +SEEK_END = 2 + + +NATIVE_ENDIAN = '=' +LITTLE_ENDIAN = '<' +BIG_ENDIAN = '>' + diff --git a/btypes/__pycache__/__init__.cpython-34.pyc b/btypes/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04d0cf3e540dc7f831324fa15d6e2e4a4b07c13a GIT binary patch literal 887 zcmY*X&2Q5%6n{?A^kXd`gvQ4y;ua3&j>g33%2Y85q8SoakQHLD4NH?^yNpfUX@mcT zJ6HY@pO851!XMBR&uRIx{a${4cAkIlXa8(>TA#mvjlL0pAMj7}5I;gwZ_sf7ik<*> zASV#*aTmA;7Z~$EIB@O)#Yhdf54;W+1gK}XQ;al#Vz3Fc4!i}l0ek~!6L=eF3wQ@c z;g!?-&q_YeMQ~WkK#GKC#W+X`9vsU|Tg(%k1WL@9kQj0=wdi-l8djXI zArQzJ38Qx`l~L%JjVGm?Cb}GnN&ZW~@6qkuDzMqbUbn8>+dwi#VEOD!?&IcqL0Eg)W06&&I`66uO(*e{HL+ zt%qNAh;9M!4&WRCflbul$N_oBN;1;K#{OHsr5#w{RT4ONAa>x)g)^_(;=%%l12@nx z%ff?C4&1(pJBiH|Izwrd4?W{)k>|$e*#}h9xVb1y&EBC1YsSa>6Uo&07#-`0sjn+C zq%cJORarJLm#J0tHEkt!4rCKN5)yeG@ufZO(h9XC50_+XNw$aKhN&~g%ak$NUg_Jd zR5s22$5hNT%3q0EWuT2wzyFlIcs?>K)H`^&M*ZO~?F@IK!K*$)JlGl1&i){Z_Ww*< lj|Y3}rGHpgucV^h*jhcMZA(*G!oJgTH{GkgPq4ez{{kdY)%ySd literal 0 HcmV?d00001 diff --git a/btypes/__pycache__/__init__.cpython-36.pyc b/btypes/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..860d4cb80c8558a3156c312f61691731064bc49a GIT binary patch literal 843 zcmYjP&u`N(6t%KZZXsLch_C;Q)RKCU?LwL=eNw zO7RfwA!ak@5;2EcbDP-g7IRts5)+4EQez%*S%cJBlXz^6G+2u?S$lwj+h3j@En8hmgr4q$R6@tso6%X4{%i?`k9(TU_jM%%@> zkSf~E&$2X+nT!TSCMQY8{o}&}e-e*QfpXN%(=?Gup7|k5R4yV)2Ztxe-7tEm&SzXg zoMefjv@<&wHk5I}qy^4HRs82M;Nc{ZKD@J-RiacvsN9d!WSmWTraB|@uNk$Kb>SAJQ|+?k6DX(clxMnTdc}j9cA-x^o)Q;k$7(|AEAAR+8jEX1g$KEG ztvX4=a5gc*>&U^6u($*G%2c~Dk}U9x1>RiXt$u)o*&M9tI;AWhQA%1%K|7VKx)FXJ z@tFeQD;29m#2bX+YkG7z&`Z=kIbNZ#zf0Qv?P2dtNCEe@`=q_!8xHsXhs~Yd-fHOv f`RZa+C-63xw`a?+m3P58mS?ZqP1nKj*v9__me141 literal 0 HcmV?d00001 diff --git a/btypes/__pycache__/big_endian.cpython-34.pyc b/btypes/__pycache__/big_endian.cpython-34.pyc new file mode 100644 index 0000000000000000000000000000000000000000..960108bfcb01ed5907c0d61ca218cdd69311a4a3 GIT binary patch literal 603 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