Skip to content

Commit

Permalink
v0.2.0
Browse files Browse the repository at this point in the history
TRI16 Support
Python 2.6 support
Performance improvments
Numerous bugfixes
  • Loading branch information
SE2Dev committed Jan 12, 2018
2 parents 0a6f7f6 + 23da875 commit 78d1095
Show file tree
Hide file tree
Showing 7 changed files with 543 additions and 120 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# PyCoD
A Python library for reading/writing Call of Duty's *_EXPORT files.
A Python library for reading/writing Call of Duty's *_EXPORT and *_bin files.

These modules are provided to make developing plugins for modeling and animation software easy, they provide reading and writing support in easy to use files that do not require any external libraries to operate.


There are currently two available plugins that utilize PyCod:
- Blender: [Blender-Cod](#)
There are currently two available plugins that utilize PyCoD:
- Blender: [Blender-CoD](https://github.com/CoDEmanX/blender-cod)
- Autodesk Maya: [CODTools](https://github.com/dtzxporter/CODTools)
5 changes: 4 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# <pep8 compliant>

from .xmodel import Model
from .xanim import Anim
from .sanim import SiegeAnim

version = (0, 1, 1) # Version specifier for PyCoD
version = (0, 2, 0) # Version specifier for PyCoD
15 changes: 7 additions & 8 deletions _lz4.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
try:
# Try to import the python-lz4 package
import lz4
import lz4.block

except:
except ImportError:
# If python-lz4 isn't present, fallback to using pure python
import sys
from io import BytesIO

try:
from six import byte2int
from six.moves import xrange
except:
except ImportError:
xrange = range

def byte2int(bytes):
return int(bytes[0])
def byte2int(_bytes):
return ord(_bytes[0])

class CorruptError(Exception):
pass
Expand Down Expand Up @@ -63,7 +62,7 @@ def get_length(src, length):
while True:
# decode a block
read_buf = src.read(1)
if len(read_buf) == 0:
if not read_buf:
raise CorruptError("EOF at reading literal-len")
token = byte2int(read_buf)

Expand All @@ -77,7 +76,7 @@ def get_length(src, length):
dst.extend(read_buf)

read_buf = src.read(2)
if len(read_buf) == 0:
if not read_buf:
if token & 0x0f != 0:
raise CorruptError(
"EOF, but match-len > 0: %u" % (token % 0x0f, ))
Expand Down
237 changes: 237 additions & 0 deletions sanim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# <pep8 compliant>

import json
import zipfile
import struct

'''
---------------------------
---< SIEGE_ANIM_SOURCE >---
---------------------------
'''

# buffer() is required for file.writestr in Python 2.x but
# is no longer required (and doesn't exist) in Python 3.x
try:
buffer
except NameError:
def buffer(data):
return data


class Frame(object):
__slots__ = ('index', 'position', 'rotation')

def __init__(self, index=0, position=(0, 0, 0), rotation=(0, 0, 0, 1)):
self.index = index
self.position = position
self.rotation = rotation


class Node(object):
__slots__ = ('name', 'frames')

def __init__(self, name=None, frames=0):
self.name = name
self.frames = [None] * int(frames)


class Shot(object):
__slots__ = ('name', 'start', 'end')

def __init__(self, name=None, start=0, end=360):
self.name = name
self.start = start
self.end = end


class Info(object):
__slots__ = ('argJson', 'computer', 'domain',
'ta_game_path', 'time', 'user')

def __init__(self, argJson="{}", computer="D3V-137", domain="ATVI",
ta_game_path="c:\\", time="", user="d3v"):
self.argJson = argJson
self.computer = computer
self.domain = domain
self.ta_game_path = ta_game_path
self.time = time
self.user = user


class SiegeAnim(object):
__slots__ = ('frames', 'nodes', 'shots',
'playback_speed', 'speed', 'loop', 'info')

def __init__(self, frames=0, nodes=0, shots=0):
self.frames = int(frames)
self.nodes = [None] * int(nodes)
self.shots = [None] * int(shots)
self.playback_speed = 1
self.speed = 0
self.loop = True
self.info = Info()

def __load_positions__(self, data):
# Load raw positions from the data buffer (3 floats 4 bytes each)
buffer_offset = 0
for frame in range(int(self.frames)):
for node in self.nodes:
trans = struct.unpack_from("fff", data, offset=buffer_offset)
node.frames[frame] = Frame(frame, trans)
buffer_offset = buffer_offset + 12

def __load_rotations__(self, data):
# Load raw rotations from the data buffer(4 floats, 4 bytes each)
buffer_offset = 0
for frame in range(int(self.frames)):
for node in self.nodes:
rot = struct.unpack_from("ffff", data, offset=buffer_offset)
node.frames[frame].rotation = rot
buffer_offset = buffer_offset + 16

def __load_index__(self, file):
# Load the serialized index file
idx_parse = json.loads(file.read("index.json"))

# All of this data is required so we must be able to load it
self.frames = int(idx_parse["animation"]["frames"])
self.loop = bool(idx_parse["animation"]["loop"])
self.nodes = [None] * int(idx_parse["animation"]["nodes"])
self.speed = int(idx_parse["animation"]["speed"])
self.playback_speed = int(idx_parse["animation"]["playbackSpeed"])

if idx_parse["info"] is not None:
if idx_parse["info"]["argJson"] is not None:
self.info.argJson = idx_parse["info"]["argJson"]
if idx_parse["info"]["computer"] is not None:
self.info.computer = idx_parse["info"]["computer"]
if idx_parse["info"]["domain"] is not None:
self.info.domain = idx_parse["info"]["domain"]
if idx_parse["info"]["ta_game_path"] is not None:
self.info.ta_game_path = idx_parse["info"]["ta_game_path"]
if idx_parse["info"]["time"] is not None:
self.info.time = idx_parse["info"]["time"]
if idx_parse["info"]["user"] is not None:
self.info.user = idx_parse["info"]["user"]

if idx_parse["nodes"] is not None:
for node_index, node in enumerate(idx_parse["nodes"]):
self.nodes[node_index] = Node(node["name"], self.frames)

if idx_parse["shots"] is not None:
self.shots = [None] * len(idx_parse["shots"])
for shot_index, shot in enumerate(idx_parse["shots"]):
self.shots[shot_index] = Shot(
shot["name"], int(shot["start"]), int(shot["end"]))

if idx_parse["data"] is not None:
if idx_parse["data"]["data/positions"] is not None:
# Load positions per node, per frame
positions = file.read("data/positions")
self.__load_positions__(positions)
if idx_parse["data"]["data/quaternions"] is not None:
# Load rotations per node, per frame
rotations = file.read("data/quaternions")
self.__load_rotations__(rotations)

def __write_positions__(self, file):
# Serialize the positions per node, per frame
byte_stride = 12 * len(self.nodes)
data_length = self.frames * len(self.nodes) * 12
data_buffer = bytearray(int(data_length))
data_offset = 0

for frame in range(int(self.frames)):
for node in self.nodes:
struct.pack_into("fff", data_buffer, data_offset,
*node.frames[frame].position)
data_offset = data_offset + 12

# Inject the data/positions file
file.writestr("data/positions", buffer(data_buffer),
compress_type=zipfile.ZIP_DEFLATED)

# Return buffer size and stride
return (data_length, byte_stride)

def __write_rotations__(self, file):
# Serialize the positions per node, per frame
byte_stride = 16 * len(self.nodes)
data_length = self.frames * len(self.nodes) * 16
data_buffer = bytearray(int(data_length))
data_offset = 0

for frame in range(int(self.frames)):
for node in self.nodes:
struct.pack_into("ffff", data_buffer, data_offset,
*node.frames[frame].rotation)
data_offset = data_offset + 16

# Inject the data/quaternions file
file.writestr("data/quaternions", buffer(data_buffer),
compress_type=zipfile.ZIP_DEFLATED)

# Return buffer size and stride
return (data_length, byte_stride)

def __write_index__(self, file):
# Serialize the data back to the file
idx_dict = {}

idx_dict["animation"] = {
"frames": str(int(self.frames)),
"loop": str(int(self.loop)),
"nodes": str(len(self.nodes)),
"playbackSpeed": str(self.playback_speed),
"speed": str(self.speed)
}

idx_dict["info"] = {
"argJson": self.info.argJson,
"computer": self.info.computer,
"domain": self.info.domain,
"ta_game_path": self.info.ta_game_path,
"time": self.info.time,
"user": self.info.user
}

idx_dict["nodes"] = [None] * len(self.nodes)
for node_index, node in enumerate(self.nodes):
idx_dict["nodes"][node_index] = {"name": node.name}

idx_dict["shots"] = [None] * len(self.shots)
for shot_index, shot in enumerate(self.shots):
idx_dict["shots"][shot_index] = {
"name": shot.name,
"end": str(shot.end), "start": str(shot.start)}

# Inject the position and rotations data
pos_data = self.__write_positions__(file)
rot_data = self.__write_rotations__(file)

# Apply the data block
idx_dict["data"] = {
"data/positions": {
"byteSize": str(pos_data[0]),
"byteStride": str(pos_data[1])
},
"data/quaternions": {
"byteSize": str(rot_data[0]),
"byteStride": str(rot_data[1])
}
}

# Inject the index file
file.writestr("index.json", json.dumps(idx_dict),
compress_type=zipfile.ZIP_DEFLATED)

def LoadFile(self, path):
file = zipfile.ZipFile(path, "r")
self.__load_index__(file)
file.close()

def WriteFile(self, path):
file = zipfile.ZipFile(path, "w")
self.__write_index__(file)
file.close()
Loading

0 comments on commit 78d1095

Please sign in to comment.