Newer
Older
mesh_chunk = _3ds_chunk(OBJECT_MESH)
# add vertex 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 uv_array:
mesh_chunk.add_subchunk(make_uv_chunk(uv_array))
# mesh_chunk.add_subchunk(make_matrix_4x3_chunk(matrix))
# create transformation matrix chunk
matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
obj_matrix = matrix.transposed().to_3x3()
if ob.parent is None:
obj_translate = translation[ob.name]
else: # Calculate child matrix translation relative to parent
obj_translate = translation[ob.name].cross(-1 * translation[ob.parent.name])
matrix_chunk.add_variable("xx", _3ds_float(obj_matrix[0].to_tuple(6)[0]))
matrix_chunk.add_variable("xy", _3ds_float(obj_matrix[0].to_tuple(6)[1]))
matrix_chunk.add_variable("xz", _3ds_float(obj_matrix[0].to_tuple(6)[2]))
matrix_chunk.add_variable("yx", _3ds_float(obj_matrix[1].to_tuple(6)[0]))
matrix_chunk.add_variable("yy", _3ds_float(obj_matrix[1].to_tuple(6)[1]))
matrix_chunk.add_variable("yz", _3ds_float(obj_matrix[1].to_tuple(6)[2]))
matrix_chunk.add_variable("zx", _3ds_float(obj_matrix[2].to_tuple(6)[0]))
matrix_chunk.add_variable("zy", _3ds_float(obj_matrix[2].to_tuple(6)[1]))
matrix_chunk.add_variable("zz", _3ds_float(obj_matrix[2].to_tuple(6)[2]))
matrix_chunk.add_variable("tx", _3ds_float(obj_translate.to_tuple(6)[0]))
matrix_chunk.add_variable("ty", _3ds_float(obj_translate.to_tuple(6)[1]))
matrix_chunk.add_variable("tz", _3ds_float(obj_translate.to_tuple(6)[2]))
mesh_chunk.add_subchunk(matrix_chunk)
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
return mesh_chunk
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
def make_kfdata(start=0, stop=0, 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("animlen", _3ds_uint(stop-start))
kfseg = _3ds_chunk(KFDATA_KFSEG)
kfseg.add_variable("start", _3ds_uint(start))
kfseg.add_variable("stop", _3ds_uint(stop))
kfcurtime = _3ds_chunk(KFDATA_KFCURTIME)
kfcurtime.add_variable("curtime", _3ds_uint(curtime))
kfdata.add_subchunk(kfhdr)
kfdata.add_subchunk(kfseg)
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."""
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())
track_chunk.add_variable("nkeys", _3ds_uint(1))
# Next section should be repeated for every keyframe, but for now, animation is not actually supported.
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)))
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.
Blender Empty objects are converted to dummy nodes."""
name = obj.name
# main object node chunk:
kf_obj_node = _3ds_chunk(KFDATA_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]))
# object node header:
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))
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:
# 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
'''
def save(operator,
context, filepath="",
use_selection=True,
global_matrix=None,
):
import time
from bpy_extras.io_utils import create_derived_objects, free_derived_objects
"""Save the Blender scene to a 3ds file."""
# Time the export
duration = time.time()
if global_matrix is None:
global_matrix = mathutils.Matrix()
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
scene = context.scene
layer = context.view_layer
#depsgraph = context.evaluated_depsgraph_get()
# Initialize the main chunk (primary):
primary = _3ds_chunk(PRIMARY)
# Add version chunk:
version_chunk = _3ds_chunk(VERSION)
version_chunk.add_variable("version", _3ds_uint(3))
primary.add_subchunk(version_chunk)
# Init main object info chunk:
object_info = _3ds_chunk(OBJECTINFO)
mesh_version = _3ds_chunk(MESHVERSION)
mesh_version.add_variable("mesh", _3ds_uint(3))
object_info.add_subchunk(mesh_version)
# Add MASTERSCALE element
mscale = _3ds_chunk(MASTERSCALE)
mscale.add_variable("scale", _3ds_float(1))
object_info.add_subchunk(mscale)
# Add AMBIENT color
if scene.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)
''' # 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):
materialDict = {}
mesh_objects = []
if use_selection:
objects = [ob for ob in scene.objects if not ob.hide_viewport and ob.select_get(view_layer=layer)]
objects = [ob for ob in scene.objects if not ob.hide_viewport]
light_objects = [ob for ob in objects if ob.type == 'LIGHT']
camera_objects = [ob for ob in objects if ob.type == 'CAMERA']
for ob in objects:
# get derived objects
free, derived = create_derived_objects(scene, ob)
if derived is None:
continue
for ob_derived, mtx in derived:
if ob.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
continue
#ob_derived_eval = ob_derived.evaluated_get(depsgraph)
data = ob_derived.to_mesh()
except:
data = None
if data:
matrix = global_matrix @ mtx
data.transform(matrix)
mesh_objects.append((ob_derived, data, matrix))
ma_ls = data.materials
ma_ls_len = len(ma_ls)
# get material/image tuples.
if data.uv_layers:
if not ma_ls:
ma = ma_name = None
for f, uf in zip(data.polygons, data.uv_layers.active.data):
if ma_ls:
ma_index = f.material_index
if ma_index >= ma_ls_len:
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
img = get_uv_image(ma)
img_name = None if img is None else img.name
materialDict.setdefault((ma_name, img_name), (ma, img))
for ma in ma_ls:
if ma: # material may be None so check its not.
materialDict.setdefault((ma.name, None), (ma, None))
for f in data.polygons:
if f.material_index >= ma_ls_len:
if free:
free_derived_objects(ob)
# Make material chunks for all materials used in the meshes:
for ma_image in materialDict.values():
object_info.add_subchunk(make_material_chunk(ma_image[0], ma_image[1]))
# Give all objects a unique ID and build a dictionary from object name to object id:
translation = {} # collect translation for transformation matrix
#name_to_id = {}
for ob, data, matrix in mesh_objects:
translation[ob.name] = ob.location
#name_to_id[ob.name]= len(name_to_id)
"""
#for ob in empty_objects:
# 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
object_chunk = _3ds_chunk(OBJECT)
# set the object name
object_chunk.add_variable("name", _3ds_string(sane_name(ob.name)))
# 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!, otherwise we cant write since the array size wont
# fit into USHORT.
if object_chunk.validate():
object_info.add_subchunk(object_chunk)
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))
'''
# if not blender_mesh.users:
# bpy.data.meshes.remove(blender_mesh)
#blender_mesh.vertices = None
i += i
# Create chunks for all empties:
''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
for ob in empty_objects:
# Empties only require a kf object node:
kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
pass
'''
# Create light object chunks
for ob in light_objects:
object_chunk = _3ds_chunk(OBJECT)
light_chunk = _3ds_chunk(OBJECT_LIGHT)
color_float_chunk = _3ds_chunk(RGB)
energy_factor = _3ds_chunk(LIGHT_MULTIPLIER)
object_chunk.add_variable("light", _3ds_string(sane_name(ob.name)))
light_chunk.add_variable("location", _3ds_point_3d(ob.location))
color_float_chunk.add_variable("color", _3ds_float_color(ob.data.color))
energy_factor.add_variable("energy", _3ds_float(ob.data.energy * .001))
light_chunk.add_subchunk(color_float_chunk)
light_chunk.add_subchunk(energy_factor)
if ob.data.type == 'SPOT':
cone_angle = math.degrees(ob.data.spot_size)
hotspot = cone_angle - (ob.data.spot_blend * math.floor(cone_angle))
hypo = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1])
pos_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2]))
pos_y = ob.location[1] + (ob.location[0] * math.tan(math.radians(90) - ob.rotation_euler[2]))
pos_z = hypo * math.tan(math.radians(90) - ob.rotation_euler[0])
spotlight_chunk = _3ds_chunk(LIGHT_SPOTLIGHT)
spot_roll_chunk = _3ds_chunk(LIGHT_SPOTROLL)
spotlight_chunk.add_variable("target", _3ds_point_3d((pos_x, pos_y, pos_z)))
spotlight_chunk.add_variable("hotspot", _3ds_float(round(hotspot, 4)))
spotlight_chunk.add_variable("angle", _3ds_float(round(cone_angle, 4)))
spot_roll_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler[1], 6)))
spotlight_chunk.add_subchunk(spot_roll_chunk)
light_chunk.add_subchunk(spotlight_chunk)
# Add light to object info
object_chunk.add_subchunk(light_chunk)
object_info.add_subchunk(object_chunk)
# Create camera object chunks
for ob in camera_objects:
object_chunk = _3ds_chunk(OBJECT)
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]))
focus_y = ob.location[1] + (ob.location[0] * math.tan(math.radians(90) - ob.rotation_euler[2]))
focus_z = diagonal * math.tan(math.radians(90) - ob.rotation_euler[0])
object_chunk.add_variable("camera", _3ds_string(sane_name(ob.name)))
camera_chunk.add_variable("location", _3ds_point_3d(ob.location))
camera_chunk.add_variable("target", _3ds_point_3d((focus_x, focus_y, focus_z)))
camera_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler[1], 6)))
camera_chunk.add_variable("lens", _3ds_float(ob.data.lens))
object_chunk.add_subchunk(camera_chunk)
object_info.add_subchunk(object_chunk)
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
# 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:
primary.add_subchunk(kfdata)
'''
# At this point, the chunk hierarchy is completely built.
# Check the size:
primary.get_size()
# Open the file for writing:
file = open(filepath, 'wb')
# Recursively write the chunks to file:
primary.write(file)
# Close the file:
file.close()
# Clear name mapping vars, could make locals too
del name_unique[:]
name_mapping.clear()
# Debugging only: report the exporting time:
print("3ds export time: %.2f" % (time.time() - duration))
# Debugging only: dump the chunk hierarchy: