ae position keys export an shift correction
0.6.5 - feat: AE key exporter (not exposed). - Add a basic 2D positions-keyframes exporter (in camera view space). export 2d position of selected object origin to copy-paste on AE layer. - feat: If 'Camera Overscan' addon is activated, append a button to fix camera shift.main
parent
2a0c173048
commit
d080a9cefa
|
@ -14,6 +14,13 @@ Activate / deactivate layer opaticty according to prefix
|
|||
Activate / deactivate all masks using MA layers
|
||||
-->
|
||||
|
||||
|
||||
0.6.5
|
||||
|
||||
- feat: AE key exporter (not exposed).
|
||||
- Add a basic 2D positions-keyframes exporter (in camera view space). export 2d position of selected object origin to copy-paste on AE layer.
|
||||
- feat: If 'Camera Overscan' addon is activated, append a button to fix camera shift.
|
||||
|
||||
0.6.4
|
||||
|
||||
- ui: render selected scene has hints on popup panekl like gen batch
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
import bpy
|
||||
# import bpy_extras
|
||||
from bpy_extras.object_utils import world_to_camera_view # as cam_space
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
import json
|
||||
from . import fn
|
||||
|
||||
'''
|
||||
def Export_AE_2d_position_json_data():
|
||||
scn = bpy.context.scene
|
||||
cam = scn.objects.get('anim_cam')
|
||||
if not cam:
|
||||
print('Active camera not "anim_cam"')
|
||||
cam = scn.camera
|
||||
|
||||
rx = scn.render
|
||||
rx, ry = rd.resolution_x, rd.resolution_y
|
||||
|
||||
targets = [o for o in bpy.context.selected_objects if o.type != 'CAMERA']
|
||||
|
||||
for ob in targets:
|
||||
# get an idea of the scale relative to image res ? (too complex since it's not even the right resolution...)
|
||||
pos = world_to_camera_view(scn, scn.objects['anim_cam'], ob.matrix_world.to_translation())[:-1]
|
||||
pix_pos = Vector((pos[0]*rx, (1-pos[1])*ry))
|
||||
'''
|
||||
|
||||
|
||||
# Unused (old func that might not be usefull at all...)
|
||||
def correct_shift(vec, cam):
|
||||
resX = bpy.context.scene.render.resolution_x
|
||||
resY = bpy.context.scene.render.resolution_y
|
||||
ratio = resX/resY
|
||||
shiftX = 2*cam.data.shift_x
|
||||
shiftY = 2*cam.data.shift_y
|
||||
|
||||
if ratio<1:
|
||||
return vec - Vector((shiftX*(1/ratio), shiftY, 0))
|
||||
elif ratio>1:
|
||||
return vec - Vector((shiftX, shiftY*ratio, 0))
|
||||
else:
|
||||
return vec - Vector((shiftX, shiftY, 0))
|
||||
|
||||
def export_AE_objects_position_keys():
|
||||
'''Export keys as paperclip to paste in after'''
|
||||
C= bpy.context
|
||||
result = {}
|
||||
|
||||
for fr in range(C.scene.frame_start,C.scene.frame_end + 1):
|
||||
|
||||
C.scene.frame_set(fr)
|
||||
|
||||
for o in C.selected_objects:
|
||||
if not result.get(o.name):
|
||||
result[o.name] = []
|
||||
proj2d = world_to_camera_view(C.scene,C.scene.camera,o.matrix_world.to_translation())# + Vector((.5,.5,0))
|
||||
|
||||
# proj2d = correct_shift(proj2d,C.scene.camera) # needed ?
|
||||
x = (proj2d[0]) * C.scene.render.resolution_x
|
||||
y = -(proj2d[1]) * C.scene.render.resolution_y + C.scene.render.resolution_y
|
||||
|
||||
result[o.name].append((fr,x,y))
|
||||
|
||||
for name,value in result.items():
|
||||
|
||||
prefix = 'Adobe After Effects 8.0 Keyframe Data\n\n'
|
||||
prefix += '\tUnits Per Second\t%s\n'%C.scene.render.fps
|
||||
prefix += '\tSource Width\t%s\n'%C.scene.render.resolution_x
|
||||
prefix += '\tSource Height\t%s\n'%C.scene.render.resolution_y
|
||||
prefix += '\tSource Pixel Aspect Ratio\t1\n'
|
||||
prefix += '\tComp Pixel Aspect Ratio\t1\n\n'
|
||||
prefix += 'Transform\tPosition\n'
|
||||
prefix += '\tFrame\tX pixels\tY pixels\tyZ pixels\t\n'
|
||||
|
||||
for v in value:
|
||||
prefix += '\t%s\t%s\t%s\t\n'%(v[0],v[1],v[2])
|
||||
|
||||
prefix += '\n\n'
|
||||
prefix += 'End of Keyframe Data\n'
|
||||
|
||||
blend = Path(bpy.data.filepath)
|
||||
keyfile = blend.parent / 'render' / f'pos_{name}.txt'
|
||||
|
||||
print(f'exporting keys for {name}')
|
||||
with open(keyfile, 'w') as fd:
|
||||
fd.write(prefix)
|
||||
|
||||
class GPEXP_OT_export_keys_to_ae(bpy.types.Operator):
|
||||
bl_idname = "gp.export_keys_to_ae"
|
||||
bl_label = "Export 2D position to AE"
|
||||
bl_description = "Export selected objects positions as text file containing key paperclip for AfterEffects layers"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def execute(self, context):
|
||||
export_AE_objects_position_keys()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GPEXP_OT_fix_overscan_shift(bpy.types.Operator):
|
||||
bl_idname = "gp.fix_overscan_shift"
|
||||
bl_label = "Fix Cam Shift Value With Overscan"
|
||||
bl_description = "(Gp render operator) change shift values to re-center overscan"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# return context.object
|
||||
|
||||
init_rx : bpy.props.IntProperty(name='pre-overscan res x')
|
||||
init_ry : bpy.props.IntProperty(name='pre-overscan res y')
|
||||
|
||||
def invoke(self, context, event):
|
||||
# if not context.object:
|
||||
# self.report({'ERROR'}, 'No object selected')
|
||||
# return {'CANCELLED'}
|
||||
|
||||
self.use_selection = False
|
||||
if context.active_object and context.active_object.type == 'CAMERA' and context.active_object != context.scene.camera:
|
||||
self.use_selection = True
|
||||
self.cam_ob = context.active_object
|
||||
else:
|
||||
self.cam_ob = context.scene.camera
|
||||
|
||||
self.init_rx = context.scene.render.resolution_x
|
||||
self.init_ry = context.scene.render.resolution_y
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
if self.use_selection:
|
||||
col = layout.column()
|
||||
col.label(text=f'Camera "{self.cam_ob.name}" selected', icon='INFO')
|
||||
col.label(text='Change in shifts will apply on this one', icon='BLANK1')
|
||||
|
||||
col = layout.column()
|
||||
col.label(text='Overscan res (current)')
|
||||
col.label(text=f'{context.scene.render.resolution_x} x {context.scene.render.resolution_y}')
|
||||
|
||||
col = layout.column()
|
||||
col.label(text='Enter Initial (pre-overscan) resolution:')
|
||||
row = col.row(align=True)
|
||||
row.prop(self, 'init_rx', text='')
|
||||
row.prop(self, 'init_ry', text='')
|
||||
|
||||
def execute(self, context):
|
||||
cam = self.cam_ob.data
|
||||
|
||||
ratio_x = self.init_rx / context.scene.render.resolution_x
|
||||
ratio_y = self.init_ry / context.scene.render.resolution_y
|
||||
|
||||
if ratio_x == 1 and ratio_y == 1:
|
||||
self.report({'ERROR'}, 'Same init and overscan resolution, nothing to change')
|
||||
return {'CANCELLED'}
|
||||
|
||||
if ratio_x != 1:
|
||||
if fn.has_keyframe(cam, 'shift_x'):
|
||||
fcu = cam.animation_data.action.fcurves.find('shift_x')
|
||||
for k in fcu.keyframe_points:
|
||||
k.y = k.y * ratio_x
|
||||
else:
|
||||
if cam.shift_x != 1:
|
||||
cam.shift_x = cam.shift_x * ratio_x
|
||||
|
||||
if ratio_y != 1:
|
||||
if fn.has_keyframe(cam, 'shift_y'):
|
||||
fcu = cam.animation_data.action.fcurves.find('shift_y')
|
||||
for k in fcu.keyframe_points:
|
||||
k.y = k.y * ratio_y
|
||||
else:
|
||||
if cam.shift_y != 1:
|
||||
cam.shift_y = cam.shift_y * ratio_y
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def overcan_shift_fix_ui(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("gp.fix_overscan_shift")
|
||||
|
||||
|
||||
classes=(
|
||||
GPEXP_OT_export_keys_to_ae,
|
||||
GPEXP_OT_fix_overscan_shift,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
if hasattr(bpy.types, 'RENDER_PT_overscan'):
|
||||
bpy.types.RENDER_PT_overscan.append(overcan_shift_fix_ui)
|
||||
|
||||
|
||||
def unregister():
|
||||
if hasattr(bpy.types, 'RENDER_PT_overscan'):
|
||||
bpy.types.RENDER_PT_overscan.remove(overcan_shift_fix_ui)
|
||||
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
|
@ -2,7 +2,7 @@ bl_info = {
|
|||
"name": "GP Render",
|
||||
"description": "Organise export of gp layers through compositor output",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 6, 4),
|
||||
"version": (0, 6, 5),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
|
@ -21,6 +21,7 @@ from . import OP_crop_to_object
|
|||
from . import OP_render_scenes
|
||||
# from . import OP_check_layer_status
|
||||
from . import OP_render_pdf
|
||||
from . import OP_export_to_ae
|
||||
from . import prefs
|
||||
from . import OP_setup_layers
|
||||
from . import ui
|
||||
|
@ -48,6 +49,7 @@ def register():
|
|||
OP_render_scenes.register()
|
||||
# OP_check_layer_status.register()
|
||||
OP_render_pdf.register()
|
||||
OP_export_to_ae.register()
|
||||
OP_setup_layers.register()
|
||||
ui.register()
|
||||
# bpy.types.Scene.pgroup_name = bpy.props.PointerProperty(type = PROJ_PGT_settings)
|
||||
|
@ -67,6 +69,7 @@ def unregister():
|
|||
ui.unregister()
|
||||
OP_setup_layers.unregister()
|
||||
# OP_check_layer_status.unregister()
|
||||
OP_export_to_ae.unregister()
|
||||
OP_render_pdf.unregister()
|
||||
OP_render_scenes.unregister()
|
||||
OP_crop_to_object.unregister()
|
||||
|
|
8
fn.py
8
fn.py
|
@ -920,6 +920,14 @@ def has_anim(ob):
|
|||
# TODO make a better check (check if there is only one key in each channel, count as not animated)
|
||||
return ob.animation_data and ob.animation_data.action
|
||||
|
||||
def has_keyframe(ob, attr):
|
||||
anim = ob.animation_data
|
||||
if anim is not None and anim.action is not None:
|
||||
for fcu in anim.action.fcurves:
|
||||
if fcu.data_path == attr:
|
||||
return len(fcu.keyframe_points) > 0
|
||||
return False
|
||||
|
||||
def get_gp_box_all_frame_selection(oblist=None, scn=None, cam=None, timeout=40):
|
||||
'''
|
||||
get points of all selection
|
||||
|
|
Loading…
Reference in New Issue