|
|
|
@ -121,6 +121,7 @@ KFDATA_KFCURTIME = 0xB009 # Frame current
|
|
|
|
|
KFDATA_KFHDR = 0xB00A # Keyframe header
|
|
|
|
|
|
|
|
|
|
# >------ sub defines of OBJECT_NODE_TAG
|
|
|
|
|
PARENT_NAME = 0x80F0 # Object parent name tree
|
|
|
|
|
OBJECT_NODE_ID = 0xB030 # Object hierachy ID
|
|
|
|
|
OBJECT_NODE_HDR = 0xB010 # Hierachy tree header
|
|
|
|
|
OBJECT_INSTANCE_NAME = 0xB011 # Object instance name
|
|
|
|
@ -149,7 +150,7 @@ def sane_name(name):
|
|
|
|
|
if name_fixed is not None:
|
|
|
|
|
return name_fixed
|
|
|
|
|
|
|
|
|
|
# strip non ascii chars
|
|
|
|
|
# Strip non ascii chars
|
|
|
|
|
new_name_clean = new_name = name.encode("ASCII", "replace").decode("ASCII")[:12]
|
|
|
|
|
i = 0
|
|
|
|
|
|
|
|
|
@ -157,7 +158,7 @@ def sane_name(name):
|
|
|
|
|
new_name = new_name_clean + '.%.3d' % i
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
|
|
# note, appending the 'str' version
|
|
|
|
|
# Note, appending the 'str' version
|
|
|
|
|
name_unique.append(new_name)
|
|
|
|
|
name_mapping[name] = new_name = new_name.encode("ASCII", "replace")
|
|
|
|
|
return new_name
|
|
|
|
@ -166,7 +167,7 @@ def sane_name(name):
|
|
|
|
|
def uv_key(uv):
|
|
|
|
|
return round(uv[0], 6), round(uv[1], 6)
|
|
|
|
|
|
|
|
|
|
# size defines
|
|
|
|
|
# Size defines
|
|
|
|
|
SZ_SHORT = 2
|
|
|
|
|
SZ_INT = 4
|
|
|
|
|
SZ_FLOAT = 4
|
|
|
|
@ -238,7 +239,7 @@ class _3ds_string(object):
|
|
|
|
|
file.write(struct.pack(binary_format, self.value))
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return str(self.value)
|
|
|
|
|
return str((self.value).decode("ASCII"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _3ds_point_3d(object):
|
|
|
|
@ -259,11 +260,11 @@ class _3ds_point_3d(object):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Used for writing a track
|
|
|
|
|
'''
|
|
|
|
|
class _3ds_point_4d(object):
|
|
|
|
|
"""Class representing a four-dimensional point for a 3ds file, for instance a quaternion."""
|
|
|
|
|
__slots__ = "w", "x", "y", "z"
|
|
|
|
|
def __init__(self, point=(0.0,0.0,0.0,0.0)):
|
|
|
|
|
|
|
|
|
|
def __init__(self, point):
|
|
|
|
|
self.w, self.x, self.y, self.z = point
|
|
|
|
|
|
|
|
|
|
def get_size(self):
|
|
|
|
@ -275,7 +276,6 @@ class _3ds_point_4d(object):
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return '(%f, %f, %f, %f)' % (self.w, self.x, self.y, self.z)
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _3ds_point_uv(object):
|
|
|
|
@ -341,7 +341,7 @@ class _3ds_face(object):
|
|
|
|
|
def get_size(self):
|
|
|
|
|
return 4 * SZ_SHORT
|
|
|
|
|
|
|
|
|
|
# no need to validate every face vert, the oversized array will catch this problem
|
|
|
|
|
# No need to validate every face vert, the oversized array will catch this problem
|
|
|
|
|
def write(self, file):
|
|
|
|
|
# The last short is used for face flags
|
|
|
|
|
file.write(struct.pack('<4H', self.vindex[0], self.vindex[1], self.vindex[2], self.flag))
|
|
|
|
@ -353,13 +353,14 @@ class _3ds_face(object):
|
|
|
|
|
class _3ds_array(object):
|
|
|
|
|
"""Class representing an array of variables for a 3ds file.
|
|
|
|
|
Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves."""
|
|
|
|
|
|
|
|
|
|
__slots__ = "values", "size"
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.values = []
|
|
|
|
|
self.size = SZ_SHORT
|
|
|
|
|
|
|
|
|
|
# add an item
|
|
|
|
|
# Add an item
|
|
|
|
|
def add(self, item):
|
|
|
|
|
self.values.append(item)
|
|
|
|
|
self.size += item.get_size()
|
|
|
|
@ -383,7 +384,6 @@ class _3ds_array(object):
|
|
|
|
|
|
|
|
|
|
class _3ds_named_variable(object):
|
|
|
|
|
"""Convenience class for named variables."""
|
|
|
|
|
|
|
|
|
|
__slots__ = "value", "name"
|
|
|
|
|
|
|
|
|
|
def __init__(self, name, val=None):
|
|
|
|
@ -408,11 +408,11 @@ class _3ds_named_variable(object):
|
|
|
|
|
self.value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# the chunk class
|
|
|
|
|
# The chunk class
|
|
|
|
|
class _3ds_chunk(object):
|
|
|
|
|
"""Class representing a chunk in a 3ds file.
|
|
|
|
|
Chunks contain zero or more variables, followed by zero or more subchunks.
|
|
|
|
|
"""
|
|
|
|
|
Chunks contain zero or more variables, followed by zero or more subchunks."""
|
|
|
|
|
|
|
|
|
|
__slots__ = "ID", "size", "variables", "subchunks"
|
|
|
|
|
|
|
|
|
|
def __init__(self, chunk_id=0):
|
|
|
|
@ -460,7 +460,7 @@ class _3ds_chunk(object):
|
|
|
|
|
"""Write the chunk to a file.
|
|
|
|
|
Uses the write function of the variables and the subchunks to do the actual work."""
|
|
|
|
|
|
|
|
|
|
# write header
|
|
|
|
|
# Write header
|
|
|
|
|
self.ID.write(file)
|
|
|
|
|
self.size.write(file)
|
|
|
|
|
for variable in self.variables:
|
|
|
|
@ -481,9 +481,9 @@ class _3ds_chunk(object):
|
|
|
|
|
subchunk.dump(indent + 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##########
|
|
|
|
|
# EXPORT #
|
|
|
|
|
##########
|
|
|
|
|
#############
|
|
|
|
|
# MATERIALS #
|
|
|
|
|
#############
|
|
|
|
|
|
|
|
|
|
def get_material_image(material):
|
|
|
|
|
""" Get images from paint slots."""
|
|
|
|
@ -514,7 +514,7 @@ def make_material_subchunk(chunk_id, color):
|
|
|
|
|
col1 = _3ds_chunk(RGB1)
|
|
|
|
|
col1.add_variable("color1", _3ds_rgb_color(color))
|
|
|
|
|
mat_sub.add_subchunk(col1)
|
|
|
|
|
# optional:
|
|
|
|
|
# Optional
|
|
|
|
|
# col2 = _3ds_chunk(RGBI)
|
|
|
|
|
# col2.add_variable("color2", _3ds_rgb_color(color))
|
|
|
|
|
# mat_sub.add_subchunk(col2)
|
|
|
|
@ -527,7 +527,7 @@ def make_percent_subchunk(chunk_id, percent):
|
|
|
|
|
pcti = _3ds_chunk(PCT)
|
|
|
|
|
pcti.add_variable("percent", _3ds_ushort(int(round(percent * 100, 0))))
|
|
|
|
|
pct_sub.add_subchunk(pcti)
|
|
|
|
|
# optional:
|
|
|
|
|
# Optional
|
|
|
|
|
# pctf = _3ds_chunk(PCTF)
|
|
|
|
|
# pctf.add_variable("pctfloat", _3ds_float(round(percent, 6)))
|
|
|
|
|
# pct_sub.add_subchunk(pctf)
|
|
|
|
@ -579,7 +579,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct):
|
|
|
|
|
0x40 activates alpha source, 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint.
|
|
|
|
|
Bits 0x80, 0x100, and 0x200 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks.
|
|
|
|
|
0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk must be accompanied with a tint bit,
|
|
|
|
|
either 0x100 or 0x200, tintcolor will be processed if tintflags are present"""
|
|
|
|
|
either 0x100 or 0x200, tintcolor will be processed if a tintflag is present"""
|
|
|
|
|
|
|
|
|
|
mapflags = 0
|
|
|
|
|
if texslot.extension == 'EXTEND':
|
|
|
|
@ -628,7 +628,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct):
|
|
|
|
|
rgb.add_variable("mapcolor", _3ds_rgb_color(spec if texslot.socket_dst.identifier == 'Specular' else base))
|
|
|
|
|
mat_sub.add_subchunk(rgb)
|
|
|
|
|
|
|
|
|
|
# store all textures for this mapto in order. This at least is what the
|
|
|
|
|
# Store all textures for this mapto in order. This at least is what the
|
|
|
|
|
# 3DS exporter did so far, afaik most readers will just skip over 2nd textures
|
|
|
|
|
for slot in texslots:
|
|
|
|
|
if slot.image is not None:
|
|
|
|
@ -730,7 +730,7 @@ def make_material_chunk(material, image):
|
|
|
|
|
if matmap:
|
|
|
|
|
material_chunk.add_subchunk(matmap)
|
|
|
|
|
|
|
|
|
|
# make sure no textures are lost. Everything that doesn't fit
|
|
|
|
|
# Make sure no textures are lost. Everything that doesn't fit
|
|
|
|
|
# into a channel is exported as secondary texture
|
|
|
|
|
diffuse = []
|
|
|
|
|
|
|
|
|
@ -757,7 +757,7 @@ def make_material_chunk(material, image):
|
|
|
|
|
material_chunk.add_subchunk(make_percent_subchunk(MATTRANS, 1 - material.diffuse_color[3]))
|
|
|
|
|
material_chunk.add_subchunk(shading)
|
|
|
|
|
|
|
|
|
|
slots = [get_material_image(material)] # can be None
|
|
|
|
|
slots = [get_material_image(material)] # Can be None
|
|
|
|
|
|
|
|
|
|
if image:
|
|
|
|
|
material_chunk.add_subchunk(make_texture_chunk(MAT_DIFFUSEMAP, slots))
|
|
|
|
@ -765,6 +765,10 @@ def make_material_chunk(material, image):
|
|
|
|
|
return material_chunk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#############
|
|
|
|
|
# MESH DATA #
|
|
|
|
|
#############
|
|
|
|
|
|
|
|
|
|
class tri_wrapper(object):
|
|
|
|
|
"""Class representing a triangle.
|
|
|
|
|
Used when converting faces to triangles"""
|
|
|
|
@ -776,7 +780,7 @@ class tri_wrapper(object):
|
|
|
|
|
self.ma = ma
|
|
|
|
|
self.image = image
|
|
|
|
|
self.faceuvs = faceuvs
|
|
|
|
|
self.offset = [0, 0, 0] # offset indices
|
|
|
|
|
self.offset = [0, 0, 0] # Offset indices
|
|
|
|
|
self.flag = flag
|
|
|
|
|
self.group = group
|
|
|
|
|
|
|
|
|
@ -844,13 +848,13 @@ def remove_face_uv(verts, tri_list):
|
|
|
|
|
need to be converted to vertex uv coordinates. That means that vertices need to be duplicated when
|
|
|
|
|
there are multiple uv coordinates per vertex."""
|
|
|
|
|
|
|
|
|
|
# initialize a list of UniqueLists, one per vertex
|
|
|
|
|
# Initialize a list of UniqueLists, one per vertex
|
|
|
|
|
unique_uvs = [{} for i in range(len(verts))]
|
|
|
|
|
|
|
|
|
|
# for each face uv coordinate, add it to the UniqueList of the vertex
|
|
|
|
|
# For each face uv coordinate, add it to the UniqueList of the vertex
|
|
|
|
|
for tri in tri_list:
|
|
|
|
|
for i in range(3):
|
|
|
|
|
# store the index into the UniqueList for future reference:
|
|
|
|
|
# Store the index into the UniqueList for future reference
|
|
|
|
|
# offset.append(uv_list[tri.vertex_index[i]].add(_3ds_point_uv(tri.faceuvs[i])))
|
|
|
|
|
|
|
|
|
|
context_uv_vert = unique_uvs[tri.vertex_index[i]]
|
|
|
|
@ -862,7 +866,7 @@ def remove_face_uv(verts, tri_list):
|
|
|
|
|
|
|
|
|
|
tri.offset[i] = offset_index__uv_3ds[0]
|
|
|
|
|
|
|
|
|
|
# At this point, each vertex has a UniqueList containing every uv coordinate associated with it only once
|
|
|
|
|
# At this point each vertex has a UniqueList containing every uv coord associated with it only once
|
|
|
|
|
# Now we need to duplicate every vertex as many times as it has uv coordinates and make sure the
|
|
|
|
|
# faces refer to the new face indices
|
|
|
|
|
vert_index = 0
|
|
|
|
@ -875,16 +879,15 @@ def remove_face_uv(verts, tri_list):
|
|
|
|
|
pt = _3ds_point_3d(vert.co) # reuse, should be ok
|
|
|
|
|
uvmap = [None] * len(unique_uvs[i])
|
|
|
|
|
for ii, uv_3ds in unique_uvs[i].values():
|
|
|
|
|
# add a vertex duplicate to the vertex_array for every uv associated with this vertex
|
|
|
|
|
# Add a vertex duplicate to the vertex_array for every uv associated with this vertex
|
|
|
|
|
vert_array.add(pt)
|
|
|
|
|
# add the uv coordinate to the uv array, this for loop does not give
|
|
|
|
|
# Add the uv coordinate to the uv array, this for loop does not give
|
|
|
|
|
# uv's ordered by ii, so we create a new map and add the uv's later
|
|
|
|
|
# uv_array.add(uv_3ds)
|
|
|
|
|
uvmap[ii] = uv_3ds
|
|
|
|
|
|
|
|
|
|
# Add the uv's in the correct order
|
|
|
|
|
# Add uv's in the correct order and add coordinates to the uv array
|
|
|
|
|
for uv_3ds in uvmap:
|
|
|
|
|
# add the uv coordinate to the uv array
|
|
|
|
|
uv_array.add(uv_3ds)
|
|
|
|
|
|
|
|
|
|
vert_index += len(unique_uvs[i])
|
|
|
|
@ -1004,23 +1007,21 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation):
|
|
|
|
|
vert_array = _3ds_array()
|
|
|
|
|
for vert in mesh.vertices:
|
|
|
|
|
vert_array.add(_3ds_point_3d(vert.co))
|
|
|
|
|
# no UV at all
|
|
|
|
|
# No UV at all
|
|
|
|
|
uv_array = None
|
|
|
|
|
|
|
|
|
|
# create the chunk
|
|
|
|
|
# Create the chunk
|
|
|
|
|
mesh_chunk = _3ds_chunk(OBJECT_MESH)
|
|
|
|
|
|
|
|
|
|
# add vertex chunk
|
|
|
|
|
# Add vertex and faces chunk
|
|
|
|
|
mesh_chunk.add_subchunk(make_vert_chunk(vert_array))
|
|
|
|
|
|
|
|
|
|
# add faces chunk
|
|
|
|
|
mesh_chunk.add_subchunk(make_faces_chunk(tri_list, mesh, materialDict))
|
|
|
|
|
|
|
|
|
|
# if available, add uv chunk
|
|
|
|
|
# If available, add uv chunk
|
|
|
|
|
if uv_array:
|
|
|
|
|
mesh_chunk.add_subchunk(make_uv_chunk(uv_array))
|
|
|
|
|
|
|
|
|
|
# create transformation matrix chunk
|
|
|
|
|
# Create transformation matrix chunk
|
|
|
|
|
matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
|
|
|
|
|
obj_matrix = matrix.transposed().to_3x3()
|
|
|
|
|
|
|
|
|
@ -1048,16 +1049,17 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation):
|
|
|
|
|
return mesh_chunk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
|
|
|
|
|
def make_kfdata(start=0, stop=0, curtime=0):
|
|
|
|
|
#################
|
|
|
|
|
# KEYFRAME DATA #
|
|
|
|
|
#################
|
|
|
|
|
|
|
|
|
|
def make_kfdata(revision, start=0, stop=100, curtime=0):
|
|
|
|
|
"""Make the basic keyframe data chunk"""
|
|
|
|
|
kfdata = _3ds_chunk(KFDATA)
|
|
|
|
|
|
|
|
|
|
kfhdr = _3ds_chunk(KFDATA_KFHDR)
|
|
|
|
|
kfhdr.add_variable("revision", _3ds_ushort(0))
|
|
|
|
|
# Not really sure what filename is used for, but it seems it is usually used
|
|
|
|
|
# to identify the program that generated the .3ds:
|
|
|
|
|
kfhdr.add_variable("filename", _3ds_string("Blender"))
|
|
|
|
|
kfhdr.add_variable("revision", _3ds_ushort(revision))
|
|
|
|
|
kfhdr.add_variable("filename", _3ds_string(b'Blender'))
|
|
|
|
|
kfhdr.add_variable("animlen", _3ds_uint(stop - start))
|
|
|
|
|
|
|
|
|
|
kfseg = _3ds_chunk(KFDATA_KFSEG)
|
|
|
|
@ -1072,107 +1074,397 @@ def make_kfdata(start=0, stop=0, curtime=0):
|
|
|
|
|
kfdata.add_subchunk(kfcurtime)
|
|
|
|
|
return kfdata
|
|
|
|
|
|
|
|
|
|
def make_track_chunk(ID, obj):
|
|
|
|
|
"""Make a chunk for track data.
|
|
|
|
|
Depending on the ID, this will construct a position, rotation or scale track."""
|
|
|
|
|
def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size):
|
|
|
|
|
"""Make a chunk for track data. Depending on the ID, this will
|
|
|
|
|
construct a position, rotation, scale, roll, color or fov track."""
|
|
|
|
|
track_chunk = _3ds_chunk(ID)
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("unknown", _3ds_uint())
|
|
|
|
|
track_chunk.add_variable("unknown", _3ds_uint())
|
|
|
|
|
|
|
|
|
|
if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG, ROLL_TRACK_TAG} and ob.animation_data and ob.animation_data.action:
|
|
|
|
|
action = ob.animation_data.action
|
|
|
|
|
if action.fcurves:
|
|
|
|
|
fcurves = action.fcurves
|
|
|
|
|
kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points]
|
|
|
|
|
nkeys = len(kframes)
|
|
|
|
|
if not 0 in kframes:
|
|
|
|
|
kframes.append(0)
|
|
|
|
|
nkeys = nkeys + 1
|
|
|
|
|
kframes = sorted(set(kframes))
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40))
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start)))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end)))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(nkeys))
|
|
|
|
|
|
|
|
|
|
if ID == POS_TRACK_TAG: # Position
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
position = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location']
|
|
|
|
|
if not position:
|
|
|
|
|
position.append(ob_pos)
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("position", _3ds_point_3d(position))
|
|
|
|
|
|
|
|
|
|
elif ID == ROT_TRACK_TAG: # Rotation
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
rotation = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler']
|
|
|
|
|
if not rotation:
|
|
|
|
|
rotation.append(ob_rot)
|
|
|
|
|
quat = mathutils.Euler(rotation).to_quaternion()
|
|
|
|
|
axis_angle = quat.angle, quat.axis[0], quat.axis[1], quat.axis[2]
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("rotation", _3ds_point_4d(axis_angle))
|
|
|
|
|
|
|
|
|
|
elif ID == SCL_TRACK_TAG: # Scale
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
size = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'scale']
|
|
|
|
|
if not size:
|
|
|
|
|
size.append(ob_size)
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("scale", _3ds_point_3d(size))
|
|
|
|
|
|
|
|
|
|
elif ID == ROLL_TRACK_TAG: # Roll
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
roll = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler']
|
|
|
|
|
if not roll:
|
|
|
|
|
roll.append(ob_rot)
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("roll", _3ds_float(round(math.degrees(roll[1]), 4)))
|
|
|
|
|
|
|
|
|
|
elif ID in {COL_TRACK_TAG, FOV_TRACK_TAG, HOTSPOT_TRACK_TAG, FALLOFF_TRACK_TAG} and ob.data.animation_data and ob.data.animation_data.action:
|
|
|
|
|
action = ob.data.animation_data.action
|
|
|
|
|
if action.fcurves:
|
|
|
|
|
fcurves = action.fcurves
|
|
|
|
|
kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points]
|
|
|
|
|
nkeys = len(kframes)
|
|
|
|
|
if not 0 in kframes:
|
|
|
|
|
kframes.append(0)
|
|
|
|
|
nkeys = nkeys + 1
|
|
|
|
|
kframes = sorted(set(kframes))
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40))
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start)))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end)))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(nkeys))
|
|
|
|
|
|
|
|
|
|
if ID == COL_TRACK_TAG: # Color
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
color = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color']
|
|
|
|
|
if not color:
|
|
|
|
|
color.append(ob.data.color[:3])
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("color", _3ds_float_color(color))
|
|
|
|
|
|
|
|
|
|
elif ID == FOV_TRACK_TAG: # Field of view
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
lens = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'lens']
|
|
|
|
|
if not lens:
|
|
|
|
|
lens.append(ob.data.lens)
|
|
|
|
|
fov = 2 * math.atan(ob.data.sensor_width/(2*lens[0]))
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("fov", _3ds_float(round(math.degrees(fov), 4)))
|
|
|
|
|
|
|
|
|
|
elif ID == HOTSPOT_TRACK_TAG: # Hotspot
|
|
|
|
|
beam_angle = math.degrees(ob.data.spot_size)
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
blend = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_blend']
|
|
|
|
|
if not blend:
|
|
|
|
|
blend.append(ob.data.spot_blend)
|
|
|
|
|
hot_spot = beam_angle-(blend[0]*math.floor(beam_angle))
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("hotspot", _3ds_float(round(hot_spot, 4)))
|
|
|
|
|
|
|
|
|
|
elif ID == FALLOFF_TRACK_TAG: # Falloff
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
fall_off = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_size']
|
|
|
|
|
if not fall_off:
|
|
|
|
|
fall_off.append(ob.data.spot_size)
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(fall_off[0]), 4)))
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(1))
|
|
|
|
|
# Next section should be repeated for every keyframe, but for now, animation is not actually supported.
|
|
|
|
|
# Next section should be repeated for every keyframe, with no animation only one tag is needed
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
if obj.type=='Empty':
|
|
|
|
|
if ID==POS_TRACK_TAG:
|
|
|
|
|
# position vector:
|
|
|
|
|
track_chunk.add_variable("position", _3ds_point_3d(obj.getLocation()))
|
|
|
|
|
elif ID==ROT_TRACK_TAG:
|
|
|
|
|
# rotation (quaternion, angle first, followed by axis):
|
|
|
|
|
q = obj.getEuler().to_quaternion() # XXX, todo!
|
|
|
|
|
track_chunk.add_variable("rotation", _3ds_point_4d((q.angle, q.axis[0], q.axis[1], q.axis[2])))
|
|
|
|
|
elif ID==SCL_TRACK_TAG:
|
|
|
|
|
# scale vector:
|
|
|
|
|
track_chunk.add_variable("scale", _3ds_point_3d(obj.getSize()))
|
|
|
|
|
else:
|
|
|
|
|
# meshes have their transformations applied before
|
|
|
|
|
# exporting, so write identity transforms here:
|
|
|
|
|
if ID==POS_TRACK_TAG:
|
|
|
|
|
# position vector:
|
|
|
|
|
track_chunk.add_variable("position", _3ds_point_3d((0.0,0.0,0.0)))
|
|
|
|
|
elif ID==ROT_TRACK_TAG:
|
|
|
|
|
# rotation (quaternion, angle first, followed by axis):
|
|
|
|
|
track_chunk.add_variable("rotation", _3ds_point_4d((0.0, 1.0, 0.0, 0.0)))
|
|
|
|
|
elif ID==SCL_TRACK_TAG:
|
|
|
|
|
# scale vector:
|
|
|
|
|
track_chunk.add_variable("scale", _3ds_point_3d((1.0, 1.0, 1.0)))
|
|
|
|
|
|
|
|
|
|
# New method simply inserts the parameters
|
|
|
|
|
if ID == POS_TRACK_TAG: # Position vector
|
|
|
|
|
track_chunk.add_variable("position", _3ds_point_3d(ob_pos))
|
|
|
|
|
|
|
|
|
|
elif ID == ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis)
|
|
|
|
|
track_chunk.add_variable("rotation", _3ds_point_4d((ob_rot.angle, ob_rot.axis[0], ob_rot.axis[1], ob_rot.axis[2])))
|
|
|
|
|
|
|
|
|
|
elif ID == SCL_TRACK_TAG: # Scale vector
|
|
|
|
|
track_chunk.add_variable("scale", _3ds_point_3d(ob_size))
|
|
|
|
|
|
|
|
|
|
elif ID == ROLL_TRACK_TAG: # Roll angle
|
|
|
|
|
track_chunk.add_variable("roll", _3ds_float(round(math.degrees(ob.rotation_euler[1]), 4)))
|
|
|
|
|
|
|
|
|
|
elif ID == COL_TRACK_TAG: # Color values
|
|
|
|
|
track_chunk.add_variable("color", _3ds_float_color(ob.data.color))
|
|
|
|
|
|
|
|
|
|
elif ID == FOV_TRACK_TAG: # Field of view
|
|
|
|
|
track_chunk.add_variable("fov", _3ds_float(round(math.degrees(ob.data.angle), 4)))
|
|
|
|
|
|
|
|
|
|
elif ID == HOTSPOT_TRACK_TAG: # Hotspot
|
|
|
|
|
beam_angle = math.degrees(ob.data.spot_size)
|
|
|
|
|
track_chunk.add_variable("hotspot", _3ds_float(round(beam_angle-(ob.data.spot_blend*math.floor(beam_angle)), 4)))
|
|
|
|
|
|
|
|
|
|
elif ID == FALLOFF_TRACK_TAG: # Falloff
|
|
|
|
|
track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(ob.data.spot_size), 4)))
|
|
|
|
|
|
|
|
|
|
return track_chunk
|
|
|
|
|
|
|
|
|
|
def make_kf_obj_node(obj, name_to_id):
|
|
|
|
|
"""Make a node chunk for a Blender object.
|
|
|
|
|
Takes the Blender object as a parameter. Object id's are taken from the dictionary name_to_id.
|
|
|
|
|
|
|
|
|
|
def make_object_node(ob, translation, rotation, scale):
|
|
|
|
|
"""Make a node chunk for a Blender object. Takes Blender object as parameter.
|
|
|
|
|
Blender Empty objects are converted to dummy nodes."""
|
|
|
|
|
|
|
|
|
|
name = obj.name
|
|
|
|
|
# main object node chunk:
|
|
|
|
|
kf_obj_node = _3ds_chunk(OBJECT_NODE_TAG)
|
|
|
|
|
# chunk for the object id:
|
|
|
|
|
obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID)
|
|
|
|
|
# object id is from the name_to_id dictionary:
|
|
|
|
|
obj_id_chunk.add_variable("node_id", _3ds_ushort(name_to_id[name]))
|
|
|
|
|
name = ob.name
|
|
|
|
|
if ob.type == 'CAMERA':
|
|
|
|
|
obj_node = _3ds_chunk(CAMERA_NODE_TAG)
|
|
|
|
|
elif ob.type == 'LIGHT':
|
|
|
|
|
obj_node = _3ds_chunk(LIGHT_NODE_TAG)
|
|
|
|
|
if ob.data.type == 'SPOT':
|
|
|
|
|
obj_node = _3ds_chunk(SPOT_NODE_TAG)
|
|
|
|
|
else: # Main object node chunk
|
|
|
|
|
obj_node = _3ds_chunk(OBJECT_NODE_TAG)
|
|
|
|
|
|
|
|
|
|
# object node header:
|
|
|
|
|
# Object node header with object name
|
|
|
|
|
obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR)
|
|
|
|
|
# object name:
|
|
|
|
|
if obj.type == 'Empty':
|
|
|
|
|
# Empties are called "$$$DUMMY" and use the OBJECT_INSTANCE_NAME chunk
|
|
|
|
|
# for their name (see below):
|
|
|
|
|
obj_node_header_chunk.add_variable("name", _3ds_string("$$$DUMMY"))
|
|
|
|
|
else:
|
|
|
|
|
# Add the name:
|
|
|
|
|
obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
|
|
|
|
|
# Add Flag variables (not sure what they do):
|
|
|
|
|
obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0))
|
|
|
|
|
parent = ob.parent
|
|
|
|
|
|
|
|
|
|
if ob.type == 'EMPTY': # Forcing to use the real name for empties
|
|
|
|
|
# Empties called $$$DUMMY and use OBJECT_INSTANCE_NAME chunk as name
|
|
|
|
|
obj_node_header_chunk.add_variable("name", _3ds_string(b"$$$DUMMY"))
|
|
|
|
|
obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000))
|
|
|
|
|
obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
|
|
|
|
|
|
|
|
|
|
# Check parent-child relationships:
|
|
|
|
|
parent = obj.parent
|
|
|
|
|
if (parent is None) or (parent.name not in name_to_id):
|
|
|
|
|
# If no parent, or the parents name is not in the name_to_id dictionary,
|
|
|
|
|
# parent id becomes -1:
|
|
|
|
|
obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1))
|
|
|
|
|
else: # Add flag variables - Based on observation flags1 is usually 0x0040 and 0x4000 for empty objects
|
|
|
|
|
obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
|
|
|
|
|
obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0040))
|
|
|
|
|
|
|
|
|
|
# Flags2 defines bit 0x01 for display path, bit 0x02 use autosmooth, bit 0x04 object frozen,
|
|
|
|
|
# bit 0x10 for motion blur, bit 0x20 for material morph and bit 0x40 for mesh morph
|
|
|
|
|
if ob.type == 'MESH' and ob.data.use_auto_smooth:
|
|
|
|
|
obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0x02))
|
|
|
|
|
else:
|
|
|
|
|
# Get the parent's id from the name_to_id dictionary:
|
|
|
|
|
obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
|
|
|
|
|
obj_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT))
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
# COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
|
|
|
|
|
# Check parent-child relationships:
|
|
|
|
|
if parent is None or parent.name not in name_to_id:
|
|
|
|
|
# If no parent, or parents name is not in dictionary, ID becomes -1:
|
|
|
|
|
obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1))
|
|
|
|
|
else: # Get the parent's ID from the name_to_id dictionary:
|
|
|
|
|
obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_to_id[parent.name]))
|
|
|
|
|
|
|
|
|
|
# Add pivot chunk:
|
|
|
|
|
obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT)
|
|
|
|
|
obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(obj.getLocation()))
|
|
|
|
|
kf_obj_node.add_subchunk(obj_pivot_chunk)
|
|
|
|
|
|
|
|
|
|
# add subchunks for object id and node header:
|
|
|
|
|
kf_obj_node.add_subchunk(obj_id_chunk)
|
|
|
|
|
kf_obj_node.add_subchunk(obj_node_header_chunk)
|
|
|
|
|
|
|
|
|
|
# Empty objects need to have an extra chunk for the instance name:
|
|
|
|
|
if obj.type == 'Empty':
|
|
|
|
|
obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME)
|
|
|
|
|
obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name)))
|
|
|
|
|
kf_obj_node.add_subchunk(obj_instance_name_chunk)
|
|
|
|
|
|
|
|
|
|
# Add track chunks for position, rotation and scale:
|
|
|
|
|
kf_obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, obj))
|
|
|
|
|
kf_obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, obj))
|
|
|
|
|
kf_obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, obj))
|
|
|
|
|
|
|
|
|
|
return kf_obj_node
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
# Add subchunk for node header
|
|
|
|
|
obj_node.add_subchunk(obj_node_header_chunk)
|
|
|
|
|
|
|
|
|
|
# Empty objects need to have an extra chunk for the instance name
|
|
|
|
|
if ob.type == 'EMPTY': # Will use a real object name for empties for now
|
|
|
|
|
obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME)
|
|
|
|
|
obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name)))
|
|
|
|
|
obj_node.add_subchunk(obj_instance_name_chunk)
|
|
|
|
|
|
|
|
|
|
if ob.type in {'MESH', 'EMPTY'}: # Add a pivot point at the object center
|
|
|
|
|
pivot_pos = (translation[name])
|
|
|
|
|
obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT)
|
|
|
|
|
obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(pivot_pos))
|
|
|
|
|
obj_node.add_subchunk(obj_pivot_chunk)
|
|
|
|
|
|
|
|
|
|
# Create a bounding box from quadrant diagonal
|
|
|
|
|
obj_boundbox = _3ds_chunk(OBJECT_BOUNDBOX)
|
|
|
|
|
obj_boundbox.add_variable("min", _3ds_point_3d(ob.bound_box[0]))
|
|
|
|
|
obj_boundbox.add_variable("max", _3ds_point_3d(ob.bound_box[6]))
|
|
|
|
|
obj_node.add_subchunk(obj_boundbox)
|
|
|
|
|
|
|
|
|
|
# Add smooth angle if autosmooth is used
|
|
|
|
|
if ob.type == 'MESH' and ob.data.use_auto_smooth:
|
|
|
|
|
obj_morph_smooth = _3ds_chunk(OBJECT_MORPH_SMOOTH)
|
|
|
|
|
obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6)))
|
|
|
|
|
obj_node.add_subchunk(obj_morph_smooth)
|
|
|
|
|
|
|
|
|
|
# Add track chunks for color, position, rotation and scale
|
|
|
|
|
if parent is None:
|
|
|
|
|
ob_pos = translation[name]
|
|
|
|
|
ob_rot = rotation[name]
|
|
|
|
|
ob_size = scale[name]
|
|
|
|
|
|
|
|
|
|
else: # Calculate child position and rotation of the object center, no scale applied
|
|
|
|
|
ob_pos = translation[name] - translation[parent.name]
|
|
|
|
|
ob_rot = rotation[name].cross(rotation[parent.name].copy().inverted())
|
|
|
|
|
ob_size = (1.0, 1.0, 1.0)
|
|
|
|
|
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
|
|
|
|
|
if ob.type in {'MESH', 'EMPTY'}:
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
if ob.type =='CAMERA':
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(FOV_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(ROLL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
if ob.type =='LIGHT':
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(COL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
if ob.type == 'LIGHT' and ob.data.type == 'SPOT':
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(HOTSPOT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(FALLOFF_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
obj_node.add_subchunk(make_track_chunk(ROLL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
|
|
|
|
|
|
|
|
|
return obj_node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_target_node(ob, translation, rotation, scale):
|
|
|
|
|
"""Make a target chunk for light and camera objects"""
|
|
|
|
|
|
|
|
|
|
name = ob.name
|
|
|
|
|
if ob.type == 'CAMERA': #Add camera target
|
|
|
|
|
tar_node = _3ds_chunk(TARGET_NODE_TAG)
|
|
|
|
|
elif ob.type == 'LIGHT': # Add spot target
|
|
|
|
|
tar_node = _3ds_chunk(LTARGET_NODE_TAG)
|
|
|
|
|
|
|
|
|
|
# Object node header with object name
|
|
|
|
|
tar_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR)
|
|
|
|
|
# Targets get the same name as the object, flags1 is usually 0x0010 and parent ROOT_OBJECT
|
|
|
|
|
tar_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
|
|
|
|
|
tar_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0010))
|
|
|
|
|
tar_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
|
|
|
|
|
tar_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT))
|
|
|
|
|
|
|
|
|
|
# Add subchunk for node header
|
|
|
|
|
tar_node.add_subchunk(tar_node_header_chunk)
|
|
|
|
|
|
|
|
|
|
# Calculate target position
|
|
|
|
|
ob_pos = translation[name]
|
|
|
|
|
ob_rot = rotation[name].to_euler()
|
|
|
|
|
ob_size = scale[name]
|
|
|
|
|
|
|
|
|
|
diagonal = math.copysign(math.sqrt(pow(ob_pos[0],2)+pow(ob_pos[1],2)), ob_pos[1])
|
|
|
|
|
target_x = ob_pos[0]+(ob_pos[1]*math.tan(ob_rot[2]))
|
|
|
|
|
target_y = ob_pos[1]+(ob_pos[0]*math.tan(math.radians(90)-ob_rot[2]))
|
|
|
|
|
target_z = -1*diagonal*math.tan(math.radians(90)-ob_rot[0])
|
|
|
|
|
|
|
|
|
|
# Add track chunks for target position
|
|
|
|
|
track_chunk = _3ds_chunk(POS_TRACK_TAG)
|
|
|
|
|
|
|
|
|
|
if ob.animation_data and ob.animation_data.action:
|
|
|
|
|
action = ob.animation_data.action
|
|
|
|
|
if action.fcurves:
|
|
|
|
|
fcurves = action.fcurves
|
|
|
|
|
kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points]
|
|
|
|
|
nkeys = len(kframes)
|
|
|
|
|
if not 0 in kframes:
|
|
|
|
|
kframes.append(0)
|
|
|
|
|
nkeys = nkeys + 1
|
|
|
|
|
kframes = sorted(set(kframes))
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40))
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start)))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end)))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(nkeys))
|
|
|
|
|
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
target_pos = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location']
|
|
|
|
|
target_rot = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler']
|
|
|
|
|
if not target_pos:
|
|
|
|
|
target_pos.append(ob_pos)
|
|
|
|
|
if not target_rot:
|
|
|
|
|
target_rot.insert(0, ob_rot.x)
|
|
|
|
|
target_rot.insert(1, ob_rot.y)
|
|
|
|
|
target_rot.insert(2, ob_rot.z)
|
|
|
|
|
diagonal = math.copysign(math.sqrt(pow(target_pos[0],2)+pow(target_pos[1],2)), target_pos[1])
|
|
|
|
|
target_x = target_pos[0]+(target_pos[1]*math.tan(target_rot[2]))
|
|
|
|
|
target_y = target_pos[1]+(target_pos[0]*math.tan(math.radians(90)-target_rot[2]))
|
|
|
|
|
target_z = -1*diagonal*math.tan(math.radians(90)-target_rot[0])
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z)))
|
|
|
|
|
|
|
|
|
|
else: # Track header
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(1))
|
|
|
|
|
# Keyframe header
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z)))
|
|
|
|
|
|
|
|
|
|
tar_node.add_subchunk(track_chunk)
|
|
|
|
|
|
|
|
|
|
return tar_node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_ambient_node(world):
|
|
|
|
|
amb_color = world.color
|
|
|
|
|
amb_node = _3ds_chunk(AMBIENT_NODE_TAG)
|
|
|
|
|
track_chunk = _3ds_chunk(COL_TRACK_TAG)
|
|
|
|
|
|
|
|
|
|
# Object node header, name is "$AMBIENT$" for ambient nodes
|
|
|
|
|
amb_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR)
|
|
|
|
|
amb_node_header_chunk.add_variable("name", _3ds_string(b"$AMBIENT$"))
|
|
|
|
|
amb_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) # Flags1 0x4000 for empty objects
|
|
|
|
|
amb_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
|
|
|
|
|
amb_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT))
|
|
|
|
|
amb_node.add_subchunk(amb_node_header_chunk)
|
|
|
|
|
|
|
|
|
|
if world.animation_data.action:
|
|
|
|
|
action = world.animation_data.action
|
|
|
|
|
if action.fcurves:
|
|
|
|
|
fcurves = action.fcurves
|
|
|
|
|
kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points]
|
|
|
|
|
nkeys = len(kframes)
|
|
|
|
|
if not 0 in kframes:
|
|
|
|
|
kframes.append(0)
|
|
|
|
|
nkeys = nkeys + 1
|
|
|
|
|
kframes = sorted(set(kframes))
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40))
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start)))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end)))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(nkeys))
|
|
|
|
|
|
|
|
|
|
for i, frame in enumerate(kframes):
|
|
|
|
|
ambient = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color']
|
|
|
|
|
if not ambient:
|
|
|
|
|
ambient.append(world.color)
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("color", _3ds_float_color(ambient))
|
|
|
|
|
|
|
|
|
|
else: # Track header
|
|
|
|
|
track_chunk.add_variable("track_flags", _3ds_ushort(0x40))
|
|
|
|
|
track_chunk.add_variable("frame_start", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("frame_total", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("nkeys", _3ds_uint(1))
|
|
|
|
|
# Keyframe header
|
|
|
|
|
track_chunk.add_variable("tcb_frame", _3ds_uint(0))
|
|
|
|
|
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
|
|
|
|
track_chunk.add_variable("color", _3ds_float_color(amb_color))
|
|
|
|
|
|
|
|
|
|
amb_node.add_subchunk(track_chunk)
|
|
|
|
|
|
|
|
|
|
return amb_node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##########
|
|
|
|
|
# EXPORT #
|
|
|
|
|
##########
|
|
|
|
|
|
|
|
|
|
def save(operator,
|
|
|
|
|
context, filepath="",
|
|
|
|
|
use_selection=True,
|
|
|
|
|
use_selection=False,
|
|
|
|
|
write_keyframe=False,
|
|
|
|
|
global_matrix=None,
|
|
|
|
|
):
|
|
|
|
|
|
|
|
|
@ -1183,6 +1475,7 @@ def save(operator,
|
|
|
|
|
scene = context.scene
|
|
|
|
|
layer = context.view_layer
|
|
|
|
|
depsgraph = context.evaluated_depsgraph_get()
|
|
|
|
|
world = scene.world
|
|
|
|
|
|
|
|
|
|
if global_matrix is None:
|
|
|
|
|
global_matrix = mathutils.Matrix()
|
|
|
|
@ -1209,21 +1502,25 @@ def save(operator,
|
|
|
|
|
mscale.add_variable("scale", _3ds_float(1))
|
|
|
|
|
object_info.add_subchunk(mscale)
|
|
|
|
|
|
|
|
|
|
# Init main keyframe data chunk
|
|
|
|
|
if write_keyframe:
|
|
|
|
|
revision = 0x0005
|
|
|
|
|
stop = scene.frame_end
|
|
|
|
|
start = scene.frame_start
|
|
|
|
|
curtime = scene.frame_current
|
|
|
|
|
kfdata = make_kfdata(revision, start, stop, curtime)
|
|
|
|
|
|
|
|
|
|
# Add AMBIENT color
|
|
|
|
|
if scene.world is not None:
|
|
|
|
|
if world is not None:
|
|
|
|
|
ambient_chunk = _3ds_chunk(AMBIENTLIGHT)
|
|
|
|
|
ambient_light = _3ds_chunk(RGB)
|
|
|
|
|
ambient_light.add_variable("ambient", _3ds_float_color(scene.world.color))
|
|
|
|
|
ambient_chunk.add_subchunk(ambient_light)
|
|
|
|
|
object_info.add_subchunk(ambient_chunk)
|
|
|
|
|
if write_keyframe and world.animation_data:
|
|
|
|
|
kfdata.add_subchunk(make_ambient_node(world))
|
|
|
|
|
|
|
|
|
|
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
|
|
|
|
|
# init main key frame data chunk:
|
|
|
|
|
kfdata = make_kfdata()
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
# Make a list of all materials used in the selected meshes (use a dictionary,
|
|
|
|
|
# each material is added once)
|
|
|
|
|
# Make a list of all materials used in the selected meshes (use dictionary, each material is added once)
|
|
|
|
|
materialDict = {}
|
|
|
|
|
mesh_objects = []
|
|
|
|
|
|
|
|
|
@ -1237,7 +1534,7 @@ def save(operator,
|
|
|
|
|
camera_objects = [ob for ob in objects if ob.type == 'CAMERA']
|
|
|
|
|
|
|
|
|
|
for ob in objects:
|
|
|
|
|
# get derived objects
|
|
|
|
|
# Get derived objects
|
|
|
|
|
derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob])
|
|
|
|
|
derived = derived_dict.get(ob)
|
|
|
|
|
|
|
|
|
@ -1260,7 +1557,7 @@ def save(operator,
|
|
|
|
|
ma_ls = data.materials
|
|
|
|
|
ma_ls_len = len(ma_ls)
|
|
|
|
|
|
|
|
|
|
# get material/image tuples
|
|
|
|
|
# Get material/image tuples
|
|
|
|
|
if data.uv_layers:
|
|
|
|
|
if not ma_ls:
|
|
|
|
|
ma = ma_name = None
|
|
|
|
@ -1272,7 +1569,7 @@ def save(operator,
|
|
|
|
|
ma_index = f.material_index = 0
|
|
|
|
|
ma = ma_ls[ma_index]
|
|
|
|
|
ma_name = None if ma is None else ma.name
|
|
|
|
|
# else there already set to none
|
|
|
|
|
# Else there already set to none
|
|
|
|
|
|
|
|
|
|
img = get_uv_image(ma)
|
|
|
|
|
img_name = None if img is None else img.name
|
|
|
|
@ -1281,7 +1578,7 @@ def save(operator,
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
for ma in ma_ls:
|
|
|
|
|
if ma: # material may be None so check its not
|
|
|
|
|
if ma: # Material may be None so check its not
|
|
|
|
|
materialDict.setdefault((ma.name, None), (ma, None))
|
|
|
|
|
|
|
|
|
|
# Why 0 Why!
|
|
|
|
@ -1296,28 +1593,34 @@ def save(operator,
|
|
|
|
|
|
|
|
|
|
# Collect translation for transformation matrix
|
|
|
|
|
translation = {}
|
|
|
|
|
rotation = {}
|
|
|
|
|
scale = {}
|
|
|
|
|
|
|
|
|
|
# Give all objects a unique ID and build a dictionary from object name to object id
|
|
|
|
|
# name_to_id = {}
|
|
|
|
|
|
|
|
|
|
for ob, data, matrix in mesh_objects:
|
|
|
|
|
translation[ob.name] = ob.location
|
|
|
|
|
rotation[ob.name] = ob.rotation_euler.to_quaternion().inverted()
|
|
|
|
|
scale[ob.name] = ob.scale
|
|
|
|
|
# name_to_id[ob.name]= len(name_to_id)
|
|
|
|
|
|
|
|
|
|
for ob in empty_objects:
|
|
|
|
|
translation[ob.name] = ob.location
|
|
|
|
|
rotation[ob.name] = ob.rotation_euler.to_quaternion().inverted()
|
|
|
|
|
scale[ob.name] = ob.scale
|
|
|
|
|
# name_to_id[ob.name]= len(name_to_id)
|
|
|
|
|
|
|
|
|
|
# Create object chunks for all meshes
|
|
|
|
|
i = 0
|
|
|
|
|
for ob, mesh, matrix in mesh_objects:
|
|
|
|
|
# create a new object chunk
|
|
|
|
|
# Create a new object chunk
|
|
|
|
|
object_chunk = _3ds_chunk(OBJECT)
|
|
|
|
|
|
|
|
|
|
# set the object name
|
|
|
|
|
# Set the object name
|
|
|
|
|
object_chunk.add_variable("name", _3ds_string(sane_name(ob.name)))
|
|
|
|
|
|
|
|
|
|
# make a mesh chunk out of the mesh
|
|
|
|
|
# Make a mesh chunk out of the mesh
|
|
|
|
|
object_chunk.add_subchunk(make_mesh_chunk(ob, mesh, matrix, materialDict, translation))
|
|
|
|
|
|
|
|
|
|
# Ensure the mesh has no over sized arrays, skip ones that do!
|
|
|
|
@ -1327,24 +1630,25 @@ def save(operator,
|
|
|
|
|
else:
|
|
|
|
|
operator.report({'WARNING'}, "Object %r can't be written into a 3DS file")
|
|
|
|
|
|
|
|
|
|
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
|
|
|
|
|
# make a kf object node for the object:
|
|
|
|
|
kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
|
|
|
|
|
'''
|
|
|
|
|
# Export kf object node
|
|
|
|
|
if write_keyframe:
|
|
|
|
|
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale))
|
|
|
|
|
|
|
|
|
|
i += i
|
|
|
|
|
|
|
|
|
|
# Create chunks for all empties
|
|
|
|
|
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
|
|
|
|
|
# Create chunks for all empties, only requires a kf object node
|
|
|
|
|
if write_keyframe:
|
|
|
|
|
for ob in empty_objects:
|
|
|
|
|
# Empties only require a kf object node:
|
|
|
|
|
kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
|
|
|
|
|
pass
|
|
|
|
|
'''
|
|
|
|
|
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale))
|
|
|
|
|
|
|
|
|
|
# Create light object chunks
|
|
|
|
|
for ob in light_objects:
|
|
|
|
|
object_chunk = _3ds_chunk(OBJECT)
|
|
|
|
|
translation[ob.name] = ob.location
|
|
|
|
|
rotation[ob.name] = ob.rotation_euler.to_quaternion()
|
|
|
|
|
scale[ob.name] = ob.scale
|
|
|
|
|
|
|
|
|
|
# Add light data subchunks
|
|
|
|
|
light_chunk = _3ds_chunk(OBJECT_LIGHT)
|
|
|
|
|
color_float_chunk = _3ds_chunk(RGB)
|
|
|
|
|
energy_factor = _3ds_chunk(LIGHT_MULTIPLIER)
|
|
|
|
@ -1381,9 +1685,20 @@ def save(operator,
|
|
|
|
|
object_chunk.add_subchunk(light_chunk)
|
|
|
|
|
object_info.add_subchunk(object_chunk)
|
|
|
|
|
|
|
|
|
|
# Export light and spotlight target node
|
|
|
|
|
if write_keyframe:
|
|
|
|
|
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale))
|
|
|
|
|
if ob.data.type == 'SPOT':
|
|
|
|
|
kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale))
|
|
|
|
|
|
|
|
|
|
# Create camera object chunks
|
|
|
|
|
for ob in camera_objects:
|
|
|
|
|
object_chunk = _3ds_chunk(OBJECT)
|
|
|
|
|
translation[ob.name] = ob.location
|
|
|
|
|
rotation[ob.name] = ob.rotation_euler.to_quaternion()
|
|
|
|
|
scale[ob.name] = ob.scale
|
|
|
|
|
|
|
|
|
|
# Add camera data subchunks
|
|
|
|
|
camera_chunk = _3ds_chunk(OBJECT_CAMERA)
|
|
|
|
|
diagonal = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1])
|
|
|
|
|
focus_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2]))
|
|
|
|
@ -1397,13 +1712,17 @@ def save(operator,
|
|
|
|
|
object_chunk.add_subchunk(camera_chunk)
|
|
|
|
|
object_info.add_subchunk(object_chunk)
|
|
|
|
|
|
|
|
|
|
# Export camera and target node
|
|
|
|
|
if write_keyframe:
|
|
|
|
|
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale))
|
|
|
|
|
kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale))
|
|
|
|
|
|
|
|
|
|
# Add main object info chunk to primary chunk
|
|
|
|
|
primary.add_subchunk(object_info)
|
|
|
|
|
|
|
|
|
|
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
|
|
|
|
|
# Add main keyframe data chunk to primary chunk:
|
|
|
|
|
# Add main keyframe data chunk to primary chunk
|
|
|
|
|
if write_keyframe:
|
|
|
|
|
primary.add_subchunk(kfdata)
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
# At this point, the chunk hierarchy is completely built
|
|
|
|
|
# Check the size
|
|
|
|
|