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 data
This commit is contained in:
		
							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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user