export anim cam 2d positionwithin bg cam
0.9.8 - feat: `Export Camera 2D Position To AE` to export 'anim cam' (or selected cam) frame center pixel coordinate within scene camera. - write txt file as after effects postion clipboard datamain
parent
d43e59b7c7
commit
c5ea98b4d0
|
@ -14,6 +14,11 @@ Activate / deactivate layer opaticty according to prefix
|
||||||
Activate / deactivate all masks using MA layers
|
Activate / deactivate all masks using MA layers
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
0.9.8
|
||||||
|
|
||||||
|
- feat: `Export Camera 2D Position To AE` to export 'anim cam' (or selected cam) frame center pixel coordinate within scene camera.
|
||||||
|
- write txt file as after effects postion clipboard data
|
||||||
|
|
||||||
0.9.7
|
0.9.7
|
||||||
|
|
||||||
- feat: `Select Nodes` added in Dopesheet. Select nodes associated with selected gp layers and report if there are errors
|
- feat: `Select Nodes` added in Dopesheet. Select nodes associated with selected gp layers and report if there are errors
|
||||||
|
|
|
@ -43,40 +43,33 @@ def correct_shift(vec, cam):
|
||||||
|
|
||||||
def export_AE_objects_position_keys():
|
def export_AE_objects_position_keys():
|
||||||
'''Export keys as paperclip to paste in after'''
|
'''Export keys as paperclip to paste in after'''
|
||||||
C= bpy.context
|
|
||||||
|
scn = bpy.context.scene
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
for fr in range(C.scene.frame_start,C.scene.frame_end + 1):
|
for fr in range(scn.frame_start,scn.frame_end + 1):
|
||||||
|
|
||||||
C.scene.frame_set(fr)
|
scn.frame_set(fr)
|
||||||
|
|
||||||
for o in C.selected_objects:
|
for o in C.selected_objects:
|
||||||
if not result.get(o.name):
|
if not result.get(o.name):
|
||||||
result[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 = world_to_camera_view(scn, scn.camera, o.matrix_world.to_translation()) # + Vector((.5,.5,0))
|
||||||
|
|
||||||
# proj2d = correct_shift(proj2d,C.scene.camera) # needed ?
|
# proj2d = correct_shift(proj2d, scn.camera) # needed ?
|
||||||
x = (proj2d[0]) * C.scene.render.resolution_x
|
x = (proj2d[0]) * scn.render.resolution_x
|
||||||
y = -(proj2d[1]) * C.scene.render.resolution_y + C.scene.render.resolution_y
|
y = -(proj2d[1]) * scn.render.resolution_y + scn.render.resolution_y
|
||||||
|
|
||||||
result[o.name].append((fr,x,y))
|
result[o.name].append((fr,x,y))
|
||||||
|
|
||||||
for name,value in result.items():
|
for name,value in result.items():
|
||||||
|
|
||||||
prefix = 'Adobe After Effects 8.0 Keyframe Data\n\n'
|
txt = fn.get_ae_keyframe_clipboard_header(scn)
|
||||||
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:
|
for v in value:
|
||||||
prefix += '\t%s\t%s\t%s\t\n'%(v[0],v[1],v[2])
|
txt += '\t%s\t%s\t%s\t\n'%(v[0],v[1],v[2])
|
||||||
|
|
||||||
prefix += '\n\n'
|
txt += '\n\nEnd of Keyframe Data\n' # keyframe txt footer
|
||||||
prefix += 'End of Keyframe Data\n'
|
|
||||||
|
|
||||||
blend = Path(bpy.data.filepath)
|
blend = Path(bpy.data.filepath)
|
||||||
keyfile = blend.parent / 'render' / f'pos_{name}.txt'
|
keyfile = blend.parent / 'render' / f'pos_{name}.txt'
|
||||||
|
@ -84,11 +77,12 @@ def export_AE_objects_position_keys():
|
||||||
|
|
||||||
print(f'exporting keys for {name}')
|
print(f'exporting keys for {name}')
|
||||||
with open(keyfile, 'w') as fd:
|
with open(keyfile, 'w') as fd:
|
||||||
fd.write(prefix)
|
fd.write(txt)
|
||||||
|
|
||||||
|
|
||||||
class GPEXP_OT_export_keys_to_ae(bpy.types.Operator):
|
class GPEXP_OT_export_keys_to_ae(bpy.types.Operator):
|
||||||
bl_idname = "gp.export_keys_to_ae"
|
bl_idname = "gp.export_keys_to_ae"
|
||||||
bl_label = "Export 2D position 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_description = "Export selected objects positions as text file containing key paperclip for AfterEffects layers"
|
||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@ -101,6 +95,53 @@ class GPEXP_OT_export_keys_to_ae(bpy.types.Operator):
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
def export_anim_cam_position(camera=None, context=None):
|
||||||
|
context = context or bpy.context
|
||||||
|
scn = context.scene
|
||||||
|
|
||||||
|
camera = camera or bpy.data.objects.get('anim_cam')
|
||||||
|
if not camera:
|
||||||
|
return 'Abort: No "anim_cam" found!'
|
||||||
|
|
||||||
|
text = fn.get_ae_keyframe_clipboard_header(scn)
|
||||||
|
for i in range(scn.frame_start, scn.frame_end + 1):
|
||||||
|
scn.frame_set(i)
|
||||||
|
center = fn.get_cam_frame_center_world(camera)
|
||||||
|
coord = fn.get_coord_in_cam_space(scn, scn.camera, center, ae=True)
|
||||||
|
# text += f'\t{i}\t{coord[0]}\t{coord[1]}\t\n'
|
||||||
|
text += f' {i} {coord[0]} {coord[1]}\n'
|
||||||
|
|
||||||
|
text += '\n\nEnd of Keyframe Data\n' # Ae Frame ending
|
||||||
|
|
||||||
|
blend = Path(bpy.data.filepath)
|
||||||
|
keyfile = blend.parent / 'render' / f'anim_cam_pos.txt'
|
||||||
|
keyfile.parent.mkdir(parents=False, exist_ok=True)
|
||||||
|
|
||||||
|
print(f'Exporting anim cam positions keys at: {keyfile}')
|
||||||
|
with open(keyfile, 'w') as fd:
|
||||||
|
fd.write(text)
|
||||||
|
|
||||||
|
class GPEXP_OT_export_cam_keys_to_ae(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.export_cam_keys_to_ae"
|
||||||
|
bl_label = "Export Camera 2D Position To AE"
|
||||||
|
bl_description = "Export anim cam 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):
|
||||||
|
cam = None
|
||||||
|
if context.object and context.object.type == 'CAMERA' and context.object != context.scene.camera:
|
||||||
|
cam = context.object
|
||||||
|
|
||||||
|
err = export_anim_cam_position(camera=cam, context=context)
|
||||||
|
if err:
|
||||||
|
self.report({'ERROR'}, err)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
class GPEXP_OT_fix_overscan_shift(bpy.types.Operator):
|
class GPEXP_OT_fix_overscan_shift(bpy.types.Operator):
|
||||||
bl_idname = "gp.fix_overscan_shift"
|
bl_idname = "gp.fix_overscan_shift"
|
||||||
bl_label = "Fix Cam Shift Value With Overscan"
|
bl_label = "Fix Cam Shift Value With Overscan"
|
||||||
|
@ -196,8 +237,10 @@ class GPEXP_PT_extra_gprender_func(bpy.types.Panel):
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.operator("gp.fix_overscan_shift")
|
col = layout.column()
|
||||||
layout.operator("gp.export_keys_to_ae")
|
col.operator("gp.fix_overscan_shift")
|
||||||
|
col.operator("gp.export_keys_to_ae")
|
||||||
|
col.operator("gp.export_cam_keys_to_ae")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -208,6 +251,7 @@ class GPEXP_PT_extra_gprender_func(bpy.types.Panel):
|
||||||
|
|
||||||
classes=(
|
classes=(
|
||||||
GPEXP_OT_export_keys_to_ae,
|
GPEXP_OT_export_keys_to_ae,
|
||||||
|
GPEXP_OT_export_cam_keys_to_ae,
|
||||||
GPEXP_OT_fix_overscan_shift,
|
GPEXP_OT_fix_overscan_shift,
|
||||||
GPEXP_PT_extra_gprender_func
|
GPEXP_PT_extra_gprender_func
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ bl_info = {
|
||||||
"name": "GP Render",
|
"name": "GP Render",
|
||||||
"description": "Organise export of gp layers through compositor output",
|
"description": "Organise export of gp layers through compositor output",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (0, 9, 7),
|
"version": (0, 9, 8),
|
||||||
"blender": (2, 93, 0),
|
"blender": (2, 93, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
|
80
fn.py
80
fn.py
|
@ -9,6 +9,9 @@ from collections import defaultdict
|
||||||
from time import time
|
from time import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
### -- node basic
|
||||||
|
|
||||||
def create_node(type, tree=None, **kargs):
|
def create_node(type, tree=None, **kargs):
|
||||||
'''Get a type, a tree to add in, and optionnaly multiple attribute to set
|
'''Get a type, a tree to add in, and optionnaly multiple attribute to set
|
||||||
return created node
|
return created node
|
||||||
|
@ -70,6 +73,8 @@ def create_aa_nodegroup(tree):
|
||||||
return ng
|
return ng
|
||||||
|
|
||||||
|
|
||||||
|
## -- object and scene settings
|
||||||
|
|
||||||
def copy_settings(obj_a, obj_b):
|
def copy_settings(obj_a, obj_b):
|
||||||
exclusion = ['bl_rna', 'id_data', 'identifier','name_property','rna_type','properties', 'stamp_note_text','use_stamp_note',
|
exclusion = ['bl_rna', 'id_data', 'identifier','name_property','rna_type','properties', 'stamp_note_text','use_stamp_note',
|
||||||
'settingsFilePath', 'settingsStamp', 'select', 'matrix_local', 'matrix_parent_inverse',
|
'settingsFilePath', 'settingsStamp', 'select', 'matrix_local', 'matrix_parent_inverse',
|
||||||
|
@ -95,7 +100,6 @@ def copy_settings(obj_a, obj_b):
|
||||||
# print(f"can't set {attr}")
|
# print(f"can't set {attr}")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def set_file_output_format(fo):
|
def set_file_output_format(fo):
|
||||||
fo.format.file_format = 'OPEN_EXR'
|
fo.format.file_format = 'OPEN_EXR'
|
||||||
fo.format.color_mode = 'RGBA'
|
fo.format.color_mode = 'RGBA'
|
||||||
|
@ -108,7 +112,6 @@ def set_file_output_format(fo):
|
||||||
# fo.format.color_depth = '8'
|
# fo.format.color_depth = '8'
|
||||||
# fo.format.compression = 15
|
# fo.format.compression = 15
|
||||||
|
|
||||||
|
|
||||||
def set_scene_aa_settings(scene=None, aa=True):
|
def set_scene_aa_settings(scene=None, aa=True):
|
||||||
'''aa == using native AA, else disable scene AA'''
|
'''aa == using native AA, else disable scene AA'''
|
||||||
if not scene:
|
if not scene:
|
||||||
|
@ -229,7 +232,8 @@ def get_view_layer(name, scene=None):
|
||||||
pass_vl = scene.view_layers.new(name)
|
pass_vl = scene.view_layers.new(name)
|
||||||
return pass_vl
|
return pass_vl
|
||||||
|
|
||||||
### node location tweaks
|
|
||||||
|
## -- node location tweaks
|
||||||
|
|
||||||
def real_loc(n):
|
def real_loc(n):
|
||||||
if not n.parent:
|
if not n.parent:
|
||||||
|
@ -260,7 +264,7 @@ def get_frame_transform(f, node_tree=None):
|
||||||
return loc, dim
|
return loc, dim
|
||||||
|
|
||||||
|
|
||||||
## get all frames with their real transform.
|
## -- get all frames with their real transform.
|
||||||
|
|
||||||
def bbox(f, frames):
|
def bbox(f, frames):
|
||||||
xs=[]
|
xs=[]
|
||||||
|
@ -322,7 +326,7 @@ def get_frames_bbox(node_tree):
|
||||||
return frames_bbox
|
return frames_bbox
|
||||||
|
|
||||||
|
|
||||||
## nodes helper functions
|
## -- nodes helper functions
|
||||||
|
|
||||||
def clear_nodegroup(name, full_clear=False):
|
def clear_nodegroup(name, full_clear=False):
|
||||||
'''remove duplication of a nodegroup (.???)
|
'''remove duplication of a nodegroup (.???)
|
||||||
|
@ -359,7 +363,6 @@ def rearrange_rlayers_in_frames(node_tree):
|
||||||
rl.location.y = top
|
rl.location.y = top
|
||||||
top -= rl.dimensions.y + 20 # place next down by height + gap of 20
|
top -= rl.dimensions.y + 20 # place next down by height + gap of 20
|
||||||
|
|
||||||
|
|
||||||
def rearrange_frames(node_tree):
|
def rearrange_frames(node_tree):
|
||||||
frame_d = get_frames_bbox(node_tree) # dic : {frame_node:(loc vector, dimensions vector), ...}
|
frame_d = get_frames_bbox(node_tree) # dic : {frame_node:(loc vector, dimensions vector), ...}
|
||||||
if not frame_d:
|
if not frame_d:
|
||||||
|
@ -616,7 +619,7 @@ def nodegroup_merge_inputs(ngroup):
|
||||||
out = ngroup.outputs.new('NodeSocketColor', ngroup.inputs[0].name)
|
out = ngroup.outputs.new('NodeSocketColor', ngroup.inputs[0].name)
|
||||||
ngroup.links.new(aa.outputs[0], ng_out.inputs[0])
|
ngroup.links.new(aa.outputs[0], ng_out.inputs[0])
|
||||||
|
|
||||||
## --- renumbering funcs ---
|
## -- renumbering funcs
|
||||||
|
|
||||||
def get_numbered_output(out, slot_name):
|
def get_numbered_output(out, slot_name):
|
||||||
'''Return output slot name without looking for numbering ???_
|
'''Return output slot name without looking for numbering ???_
|
||||||
|
@ -911,6 +914,8 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
|
||||||
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
|
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
|
||||||
|
|
||||||
|
|
||||||
|
## -- camera framing and object anim checks
|
||||||
|
|
||||||
def get_bbox_3d(ob):
|
def get_bbox_3d(ob):
|
||||||
bbox_coords = ob.bound_box
|
bbox_coords = ob.bound_box
|
||||||
return [ob.matrix_world @ Vector(b) for b in bbox_coords]
|
return [ob.matrix_world @ Vector(b) for b in bbox_coords]
|
||||||
|
@ -1032,7 +1037,6 @@ def set_border_region_from_coord(coords, scn=None, margin=30, export_json=True):
|
||||||
# export_crop_to_json(scn)
|
# export_crop_to_json(scn)
|
||||||
return pixel_bbox2d_coords
|
return pixel_bbox2d_coords
|
||||||
|
|
||||||
|
|
||||||
def get_gp_box_all_frame(ob, cam=None):
|
def get_gp_box_all_frame(ob, cam=None):
|
||||||
'''set crop to object bounding box considering whole animation. Cam should not be animated (render in bg_cam)
|
'''set crop to object bounding box considering whole animation. Cam should not be animated (render in bg_cam)
|
||||||
return 2d bbox in pixels
|
return 2d bbox in pixels
|
||||||
|
@ -1159,7 +1163,6 @@ def get_bbox_2d(ob, cam=None):
|
||||||
|
|
||||||
return [Vector(b) for b in bbox2d_coords]
|
return [Vector(b) for b in bbox2d_coords]
|
||||||
|
|
||||||
|
|
||||||
def set_box_from_selected_objects(scn=None, cam=None, export_json=False):
|
def set_box_from_selected_objects(scn=None, cam=None, export_json=False):
|
||||||
scn = scn or bpy.context.scene
|
scn = scn or bpy.context.scene
|
||||||
cam = cam or scn.camera
|
cam = cam or scn.camera
|
||||||
|
@ -1171,6 +1174,65 @@ def set_box_from_selected_objects(scn=None, cam=None, export_json=False):
|
||||||
|
|
||||||
_bbox_px = set_border_region_from_coord(coords, margin=30, scn=scn, export_json=export_json)
|
_bbox_px = set_border_region_from_coord(coords, margin=30, scn=scn, export_json=export_json)
|
||||||
|
|
||||||
|
def get_cam_frame_center_world(cam):
|
||||||
|
'''get camera frame center world position in 3d space'''
|
||||||
|
## ortho cam note: scale must be 1,1,1 (parent too) to fit right in cam-frame rectangle
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
frame = cam.data.view_frame()
|
||||||
|
mat = cam.matrix_world
|
||||||
|
frame = [mat @ v for v in frame]
|
||||||
|
|
||||||
|
# return np.add.reduce(frame) / 4
|
||||||
|
return Vector(np.sum(frame, axis=0) / 4)
|
||||||
|
|
||||||
|
def get_coord_in_cam_space(scene, cam_ob, co, ae=False):
|
||||||
|
'''Get 2d coordinate of vector in cam space
|
||||||
|
:scene: scene where camera is used (needed to get resolution)
|
||||||
|
:cam_ob: camera object
|
||||||
|
:co: the Vector3 coordinate to find in cam space
|
||||||
|
:ae: if True, Return after effects coord, top-left corner origin (blender is bottom-left)
|
||||||
|
|
||||||
|
'''
|
||||||
|
import bpy_extras
|
||||||
|
co_2d = bpy_extras.object_utils.world_to_camera_view(scene, cam_ob, co)
|
||||||
|
|
||||||
|
if ae:
|
||||||
|
# y coordinate from top
|
||||||
|
co_2d = Vector((co_2d.x, 1 - co_2d.y))
|
||||||
|
|
||||||
|
## Convert to pixel values based on scene resolution and percentage
|
||||||
|
render_scale = scene.render.resolution_percentage / 100
|
||||||
|
render_size = (
|
||||||
|
int(scene.render.resolution_x * render_scale),
|
||||||
|
int(scene.render.resolution_y * render_scale),
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
round(co_2d.x * render_size[0]), # x
|
||||||
|
round(co_2d.y * render_size[1]), # y
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## -- After effects exports
|
||||||
|
|
||||||
|
def get_ae_keyframe_clipboard_header(scn):
|
||||||
|
import textwrap
|
||||||
|
t = f'''\
|
||||||
|
Adobe After Effects 8.0 Keyframe Data
|
||||||
|
|
||||||
|
Units Per Second {scn.render.fps}
|
||||||
|
Source Width {scn.render.resolution_x}
|
||||||
|
Source Height {scn.render.resolution_y}
|
||||||
|
Source Pixel Aspect Ratio 1
|
||||||
|
Comp Pixel Aspect Ratio 1
|
||||||
|
|
||||||
|
Transform Position
|
||||||
|
Frame X pixels Y pixels Z pixels
|
||||||
|
'''
|
||||||
|
return textwrap.dedent(t)
|
||||||
|
|
||||||
|
## -- Collection handle
|
||||||
|
|
||||||
def get_collection_childs_recursive(col, cols=[], include_root=True):
|
def get_collection_childs_recursive(col, cols=[], include_root=True):
|
||||||
'''return a list of all the sub-collections in passed col'''
|
'''return a list of all the sub-collections in passed col'''
|
||||||
|
|
Loading…
Reference in New Issue