3
11

io_scene_3ds: Update for Blender 3.x #2

Merged
Sebastian Sille merged 34 commits from blender-v3.5-release into blender-v3.1-release 2023-02-17 22:45:58 +01:00
Showing only changes of commit 5c634a4378 - Show all commits

View File

@ -40,31 +40,6 @@ from math import degrees, floor
from mathutils import Matrix, Vector, Color from mathutils import Matrix, Vector, Color
def get_comp_data(context):
"""Create list of static blender's data"""
scene = context.scene
aspect_x = scene.render.pixel_aspect_x
aspect_y = scene.render.pixel_aspect_y
aspect = aspect_x / aspect_y
start = scene.frame_start
end = scene.frame_end
active_cam_frames = get_active_cam_for_each_frame(scene, start, end)
fps = scene.render.fps / scene.render.fps_base
return {
'scn': scene,
'width': scene.render.resolution_x,
'height': scene.render.resolution_y,
'aspect': aspect,
'fps': fps,
'start': start,
'end': end,
'duration': (end - start + 1.0) / fps,
'active_cam_frames': active_cam_frames,
'frame_current': scene.frame_current,
}
def get_active_cam_for_each_frame(scene, start, end): def get_active_cam_for_each_frame(scene, start, end):
"""Create list of active camera for each frame in case active camera is set by markers""" """Create list of active camera for each frame in case active camera is set by markers"""
active_cam_frames = [] active_cam_frames = []
@ -107,8 +82,8 @@ class ObjectExport():
self.name_ae = convert_name(self.obj.name) self.name_ae = convert_name(self.obj.name)
self.keyframes = {} self.keyframes = {}
def get_prop_keyframe(self, context, prop_name, value, time): def get_prop_keyframe(self, prop_name, value, time):
"""Set keyframe for given property""" """Get keyframe for given property, only if different from previous value"""
prop_keys = self.keyframes.setdefault(prop_name, []) prop_keys = self.keyframes.setdefault(prop_name, [])
if len(prop_keys) == 0: if len(prop_keys) == 0:
prop_keys.append([time, value, False]) prop_keys.append([time, value, False])
@ -121,19 +96,19 @@ class ObjectExport():
else: else:
prop_keys[-1][2] = True prop_keys[-1][2] = True
def get_keyframe(self, context, data, time, ae_size): def get_keyframe(self, context, width, height, aspect, time, ae_size):
"""Store animation for the current frame""" """Store animation for the current frame"""
ae_transform = convert_transform_matrix(self.obj.matrix_world, ae_transform = convert_transform_matrix(self.obj.matrix_world,
data['width'], data['height'], width, height,
data['aspect'], True, ae_size) aspect, True, ae_size)
self.get_prop_keyframe(context, 'position', ae_transform[0:3], time) self.get_prop_keyframe('position', ae_transform[0:3], time)
self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time) self.get_prop_keyframe('orientation', ae_transform[3:6], time)
self.get_prop_keyframe(context, 'scale', ae_transform[6:9], time) self.get_prop_keyframe('scale', ae_transform[6:9], time)
def get_obj_script(self, include_animation): def get_obj_script(self, include_animation):
"""Get the JSX script for the object""" """Get the JSX script for the object"""
return self.get_type_script() + self.get_prop_script(include_animation) + self.get_post_script() return self.get_type_script() + self.get_anim_script(include_animation) + self.get_post_script()
def get_type_script(self): def get_type_script(self):
"""Get the basic part of the JSX script""" """Get the basic part of the JSX script"""
@ -142,22 +117,22 @@ class ObjectExport():
type_script += f'{self.name_ae}.source.name = "{self.name_ae}";\n' type_script += f'{self.name_ae}.source.name = "{self.name_ae}";\n'
return type_script return type_script
def get_prop_script(self, include_animation): def get_anim_script(self, include_animation):
"""Get the part of the JSX script encoding animation""" """Get the part of the JSX script encoding animation"""
prop_script = "" anim_script = ""
# Set values of properties, add keyframes only where needed # Set values of properties, add keyframes only where needed
for prop, keys in self.keyframes.items(): for prop, keys in self.keyframes.items():
if include_animation and len(keys) > 1: if include_animation and len(keys) > 1:
times = ",".join(str(k[0]) for k in keys) times = ",".join(str(k[0]) for k in keys)
values = ",".join(str(k[1]) for k in keys).replace(" ", "") values = ",".join(str(k[1]) for k in keys).replace(" ", "")
prop_script += ( anim_script += (
f'{self.name_ae}.property("{prop}").setValuesAtTimes([{times}],[{values}]);\n') f'{self.name_ae}.property("{prop}").setValuesAtTimes([{times}],[{values}]);\n')
# Set to HOLD the frames after which animation is fixed # Set to HOLD the frames after which animation is fixed
# for several frames, to avoid interpolation errors # for several frames, to avoid interpolation errors
if any(k[2] for k in keys): if any(k[2] for k in keys):
prop_script += ( anim_script += (
f'var hold_frames = {[i + 1 for i, k in enumerate(keys) if k[2]]};\n' f'var hold_frames = {[i + 1 for i, k in enumerate(keys) if k[2]]};\n'
'for (var i = 0; i < hold_frames.length; i++) {\n' 'for (var i = 0; i < hold_frames.length; i++) {\n'
f' {self.name_ae}.property("{prop}").setInterpolationTypeAtKey(hold_frames[i], KeyframeInterpolationType.HOLD);\n' f' {self.name_ae}.property("{prop}").setInterpolationTypeAtKey(hold_frames[i], KeyframeInterpolationType.HOLD);\n'
@ -166,27 +141,28 @@ class ObjectExport():
# No animation for this property # No animation for this property
else: else:
value = str(keys[0][1]).replace(" ", "") value = str(keys[0][1]).replace(" ", "")
prop_script += ( anim_script += (
f'{self.name_ae}.property("{prop}").setValue({value});\n') f'{self.name_ae}.property("{prop}").setValue({value});\n')
prop_script += '\n'
return prop_script anim_script += '\n'
return anim_script
def get_post_script(self): def get_post_script(self):
"""This is only used in lights as a post-treatment after animation""" """This is only used in lights as a post-treatment after animation"""
return "" return ""
class CameraExport(ObjectExport): class CameraExport(ObjectExport):
def get_keyframe(self, context, data, time, ae_size): def get_keyframe(self, context, width, height, aspect, time, ae_size):
ae_transform = convert_transform_matrix(self.obj.matrix_world, ae_transform = convert_transform_matrix(self.obj.matrix_world,
data['width'], data['height'], width, height,
data['aspect'], True, ae_size) aspect, True, ae_size)
zoom = convert_lens(self.obj, data['width'], data['height'], zoom = convert_lens(self.obj, width, height,
data['aspect']) aspect)
self.get_prop_keyframe(context, 'position', ae_transform[0:3], time) self.get_prop_keyframe('position', ae_transform[0:3], time)
self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time) self.get_prop_keyframe('orientation', ae_transform[3:6], time)
self.get_prop_keyframe(context, 'zoom', zoom, time) self.get_prop_keyframe('zoom', zoom, time)
def get_type_script(self): def get_type_script(self):
type_script = f'var {self.name_ae} = newComp.layers.addCamera("{self.name_ae}",[0,0]);\n' type_script = f'var {self.name_ae} = newComp.layers.addCamera("{self.name_ae}",[0,0]);\n'
@ -195,24 +171,24 @@ class CameraExport(ObjectExport):
class LightExport(ObjectExport): class LightExport(ObjectExport):
def get_keyframe(self, context, data, time, ae_size): def get_keyframe(self, context, width, height, aspect, time, ae_size):
ae_transform = convert_transform_matrix(self.obj.matrix_world, ae_transform = convert_transform_matrix(self.obj.matrix_world,
data['width'], data['height'], width, height,
data['aspect'], True, ae_size) aspect, True, ae_size)
self.type = self.obj.data.type self.type = self.obj.data.type
color = list(self.obj.data.color) color = list(self.obj.data.color)
intensity = self.obj.data.energy * 10.0 intensity = self.obj.data.energy * 10.0
self.get_prop_keyframe(context, 'position', ae_transform[0:3], time) self.get_prop_keyframe('position', ae_transform[0:3], time)
if self.type in {'SPOT', 'SUN'}: if self.type in {'SPOT', 'SUN'}:
self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time) self.get_prop_keyframe('orientation', ae_transform[3:6], time)
self.get_prop_keyframe(context, 'intensity', intensity, time) self.get_prop_keyframe('intensity', intensity, time)
self.get_prop_keyframe(context, 'Color', color, time) self.get_prop_keyframe('Color', color, time)
if self.type == 'SPOT': if self.type == 'SPOT':
cone_angle = degrees(self.obj.data.spot_size) cone_angle = degrees(self.obj.data.spot_size)
self.get_prop_keyframe(context, 'Cone Angle', cone_angle, time) self.get_prop_keyframe('Cone Angle', cone_angle, time)
cone_feather = self.obj.data.spot_blend * 100.0 cone_feather = self.obj.data.spot_blend * 100.0
self.get_prop_keyframe(context, 'Cone Feather', cone_feather, time) self.get_prop_keyframe('Cone Feather', cone_feather, time)
def get_type_script(self): def get_type_script(self):
type_script = f'var {self.name_ae} = newComp.layers.addLight("{self.name_ae}", [0.0, 0.0]);\n' type_script = f'var {self.name_ae} = newComp.layers.addLight("{self.name_ae}", [0.0, 0.0]);\n'
@ -232,14 +208,14 @@ class LightExport(ObjectExport):
class ImageExport(ObjectExport): class ImageExport(ObjectExport):
def get_keyframe(self, context, data, time, ae_size): def get_keyframe(self, context, width, height, aspect, time, ae_size):
# Convert obj transform properties to AE space # Convert obj transform properties to AE space
plane_matrix = get_image_plane_matrix(self.obj) plane_matrix = get_image_plane_matrix(self.obj)
# Scale plane to account for AE's transforms # Scale plane to account for AE's transforms
plane_matrix = plane_matrix @ Matrix.Scale(100.0 / data['width'], 4) plane_matrix = plane_matrix @ Matrix.Scale(100.0 / width, 4)
ae_transform = convert_transform_matrix(plane_matrix, data['width'], ae_transform = convert_transform_matrix(plane_matrix, width,
data['height'], data['aspect'], height, aspect,
True, ae_size) True, ae_size)
opacity = 0.0 if self.obj.hide_render else 100.0 opacity = 0.0 if self.obj.hide_render else 100.0
@ -247,7 +223,7 @@ class ImageExport(ObjectExport):
self.filepath = get_image_filepath(self.obj) self.filepath = get_image_filepath(self.obj)
image_width, image_height = get_image_size(self.obj) image_width, image_height = get_image_size(self.obj)
ratio_to_comp = image_width / data['width'] ratio_to_comp = image_width / width
scale = ae_transform[6:9] scale = ae_transform[6:9]
if image_height != 0.0: if image_height != 0.0:
scale[1] *= image_width / image_height scale[1] *= image_width / image_height
@ -255,10 +231,10 @@ class ImageExport(ObjectExport):
scale[0] /= ratio_to_comp scale[0] /= ratio_to_comp
scale[1] /= ratio_to_comp scale[1] /= ratio_to_comp
self.get_prop_keyframe(context, 'position', ae_transform[0:3], time) self.get_prop_keyframe('position', ae_transform[0:3], time)
self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time) self.get_prop_keyframe('orientation', ae_transform[3:6], time)
self.get_prop_keyframe(context, 'scale', scale, time) self.get_prop_keyframe('scale', scale, time)
self.get_prop_keyframe(context, 'opacity', opacity, time) self.get_prop_keyframe('opacity', opacity, time)
def get_type_script(self): def get_type_script(self):
type_script = f'var newFootage = app.project.importFile(new ImportOptions(File("{self.filepath}")));\n' type_script = f'var newFootage = app.project.importFile(new ImportOptions(File("{self.filepath}")));\n'
@ -270,30 +246,30 @@ class ImageExport(ObjectExport):
class SolidExport(ObjectExport): class SolidExport(ObjectExport):
def get_keyframe(self, context, data, time, ae_size): def get_keyframe(self, context, width, height, aspect, time, ae_size):
# Convert obj transform properties to AE space # Convert obj transform properties to AE space
plane_matrix = get_plane_matrix(self.obj) plane_matrix = get_plane_matrix(self.obj)
# Scale plane to account for AE's transforms # Scale plane to account for AE's transforms
plane_matrix = plane_matrix @ Matrix.Scale(100.0 / data['width'], 4) plane_matrix = plane_matrix @ Matrix.Scale(100.0 / width, 4)
ae_transform = convert_transform_matrix(plane_matrix, data['width'], ae_transform = convert_transform_matrix(plane_matrix, width,
data['height'], data['aspect'], height, aspect,
True, ae_size) True, ae_size)
opacity = 0.0 if self.obj.hide_render else 100.0 opacity = 0.0 if self.obj.hide_render else 100.0
if not hasattr(self, 'color'): if not hasattr(self, 'color'):
self.color = get_plane_color(self.obj) self.color = get_plane_color(self.obj)
if not hasattr(self, 'width'): if not hasattr(self, 'width'):
self.width = data['width'] self.width = width
if not hasattr(self, 'height'): if not hasattr(self, 'height'):
self.height = data['height'] self.height = height
scale = ae_transform[6:9] scale = ae_transform[6:9]
scale[1] *= data['width'] / data['height'] scale[1] *= width / height
self.get_prop_keyframe(context, 'position', ae_transform[0:3], time) self.get_prop_keyframe('position', ae_transform[0:3], time)
self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time) self.get_prop_keyframe('orientation', ae_transform[3:6], time)
self.get_prop_keyframe(context, 'scale', scale, time) self.get_prop_keyframe('scale', scale, time)
self.get_prop_keyframe(context, 'opacity', opacity, time) self.get_prop_keyframe('opacity', opacity, time)
def get_type_script(self): def get_type_script(self):
type_script = f'var {self.name_ae} = newComp.layers.addSolid({self.color},"{self.name_ae}",{self.width},{self.height},1.0);\n' type_script = f'var {self.name_ae} = newComp.layers.addSolid({self.color},"{self.name_ae}",{self.width},{self.height},1.0);\n'
@ -310,18 +286,18 @@ class CamBundleExport(ObjectExport):
self.name_ae = convert_name(f'{obj.name}__{track.name}') self.name_ae = convert_name(f'{obj.name}__{track.name}')
self.keyframes = {} self.keyframes = {}
def get_keyframe(self, context, data, time, ae_size): def get_keyframe(self, context, width, height, aspect, time, ae_size):
# Bundles are in camera space. # Bundles are in camera space.
# Transpose to world space # Transpose to world space
matrix = Matrix.Translation(self.obj.matrix_basis matrix = Matrix.Translation(self.obj.matrix_basis
@ self.track.bundle) @ self.track.bundle)
# Convert the position into AE space # Convert the position into AE space
ae_transform = convert_transform_matrix(matrix, data['width'], ae_transform = convert_transform_matrix(matrix, width,
data['height'], height,
data['aspect'], False, aspect, False,
ae_size) ae_size)
self.get_prop_keyframe(context, 'position', ae_transform[0:3], time) self.get_prop_keyframe('position', ae_transform[0:3], time)
def get_type_script(self): def get_type_script(self):
type_script = f'var {self.name_ae} = newComp.layers.addNull();\n' type_script = f'var {self.name_ae} = newComp.layers.addNull();\n'
@ -672,44 +648,52 @@ def convert_lens(camera, width, height, aspect):
# return matrix # return matrix
def write_jsx_file(context, file, data, selection, include_animation, ae_size): def write_jsx_file(context, file, selection, include_animation, ae_size):
"""jsx script for AE creation""" """jsx script for AE creation"""
print("\n---------------------------\n" print("\n---------------------------\n"
"- Export to After Effects -\n" "- Export to After Effects -\n"
"---------------------------") "---------------------------")
# Store the current frame to restore it at the end of export # Create list of static blender data
frame_current = data['frame_current'] scene = context.scene
width = scene.render.resolution_x
# Get all keyframes for each object and store in dico height = scene.render.resolution_y
aspect_x = scene.render.pixel_aspect_x
aspect_y = scene.render.pixel_aspect_y
aspect = aspect_x / aspect_y
if include_animation: if include_animation:
end = data['end'] + 1 frame_end = scene.frame_end + 1
else: else:
end = data['start'] + 1 frame_end = scene.frame_start + 1
fps = scene.render.fps / scene.render.fps_base
duration = (frame_end - scene.frame_start) / fps
for frame in range(data['start'], end): # Store the current frame to restore it at the end of export
frame_current = scene.frame_current
# Get all keyframes for each object
for frame in range(scene.frame_start, frame_end):
print("Working on frame: " + str(frame)) print("Working on frame: " + str(frame))
data['scn'].frame_set(frame) scene.frame_set(frame)
# Get time for this loop # Get time for this loop
time = (frame - data['start']) / data['fps'] time = (frame - scene.frame_start) / fps
for obj_type in selection.values(): for obj_type in selection.values():
for obj in obj_type: for obj in obj_type:
obj.get_keyframe(context, data, time, ae_size) obj.get_keyframe(context, width, height, aspect, time, ae_size)
# ---- write JSX file # ---- write JSX file
with open(file, 'w') as jsx_file: with open(file, 'w') as jsx_file:
# Make the jsx executable in After Effects (enable double click on jsx) # Make the jsx executable in After Effects (enable double click on jsx)
jsx_file.write('#target AfterEffects\n\n') jsx_file.write('#target AfterEffects\n\n')
# Script's header # Script's header
jsx_file.write('/**************************************\n') jsx_file.write('/**************************************\n')
jsx_file.write(f'Scene : {data["scn"].name}\n') jsx_file.write(f'Scene : {scene.name}\n')
jsx_file.write(f'Resolution : {data["width"]} x {data["height"]}\n') jsx_file.write(f'Resolution : {width} x {height}\n')
jsx_file.write(f'Duration : {data["duration"]}\n') jsx_file.write(f'Duration : {duration}\n')
jsx_file.write(f'FPS : {data["fps"]}\n') jsx_file.write(f'FPS : {fps}\n')
jsx_file.write(f'Date : {datetime.datetime.now()}\n') jsx_file.write(f'Date : {datetime.datetime.now()}\n')
jsx_file.write(f'Exported with io_export_after_effects.py\n') jsx_file.write(f'Exported with io_export_after_effects.py\n')
jsx_file.write(f'**************************************/\n\n\n\n') jsx_file.write(f'**************************************/\n\n\n\n')
@ -727,12 +711,13 @@ def write_jsx_file(context, file, data, selection, include_animation, ae_size):
jsx_file.write('if (compName){') jsx_file.write('if (compName){')
# Continue only if comp name is given. If not - terminate # Continue only if comp name is given. If not - terminate
jsx_file.write( jsx_file.write(
f'\nvar newComp = app.project.items.addComp(compName, {data["width"]}, ' f'\nvar newComp = app.project.items.addComp(compName, {width}, '
f'{data["height"]}, {data["aspect"]}, {data["duration"]}, {data["fps"]});') f'{height}, {aspect}, {duration}, {fps});')
jsx_file.write(f"\nnewComp.displayStartTime = {(data['start']) / data['fps']};\n\n") jsx_file.write(f"\nnewComp.displayStartTime = {scene.frame_start / fps};\n\n")
jsx_file.write('var footageFolder = app.project.items.addFolder(compName + "_layers")\n\n\n') jsx_file.write('var footageFolder = app.project.items.addFolder(compName + "_layers")\n\n\n')
# Write each object's creation script
for obj_type in ('cam_bundles', 'nulls', 'solids', 'images', 'lights', 'cameras'): for obj_type in ('cam_bundles', 'nulls', 'solids', 'images', 'lights', 'cameras'):
if len(selection[obj_type]): if len(selection[obj_type]):
type_name = 'CAMERA 3D MARKERS' if obj_type == 'cam_bundles' else obj_type.upper() type_name = 'CAMERA 3D MARKERS' if obj_type == 'cam_bundles' else obj_type.upper()
@ -750,8 +735,8 @@ def write_jsx_file(context, file, data, selection, include_animation, ae_size):
jsx_file.write('compFromBlender();\n') # Execute function jsx_file.write('compFromBlender();\n') # Execute function
jsx_file.write('app.endUndoGroup();\n\n\n') jsx_file.write('app.endUndoGroup();\n\n\n')
# Set current frame of animation in blender to state before export # Restore current frame of animation in blender to state before export
data['scn'].frame_set(frame_current) scene.frame_set(frame_current)
########################################## ##########################################
@ -850,14 +835,13 @@ class ExportJsx(bpy.types.Operator, ExportHelper):
return selected or camera return selected or camera
def execute(self, context): def execute(self, context):
data = get_comp_data(context)
selection = get_selected(context, self.include_active_cam, selection = get_selected(context, self.include_active_cam,
self.include_selected_cams, self.include_selected_cams,
self.include_selected_objects, self.include_selected_objects,
self.include_cam_bundles, self.include_cam_bundles,
self.include_image_planes, self.include_image_planes,
self.include_solids) self.include_solids)
write_jsx_file(context, self.filepath, data, selection, write_jsx_file(context, self.filepath, selection,
self.include_animation, self.ae_size) self.include_animation, self.ae_size)
print("\nExport to After Effects Completed") print("\nExport to After Effects Completed")
return {'FINISHED'} return {'FINISHED'}