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''' scn = bpy.context.scene result = {} for fr in range(scn.frame_start,scn.frame_end + 1): scn.frame_set(fr) for o in C.selected_objects: if not result.get(o.name): result[o.name] = [] proj2d = world_to_camera_view(scn, scn.camera, o.matrix_world.to_translation()) # + Vector((.5,.5,0)) # proj2d = correct_shift(proj2d, scn.camera) # needed ? x = (proj2d[0]) * scn.render.resolution_x y = -(proj2d[1]) * scn.render.resolution_y + scn.render.resolution_y result[o.name].append((fr,x,y)) for name,value in result.items(): txt = fn.get_ae_keyframe_clipboard_header(scn) for v in value: txt += '\t%s\t%s\t%s\t\n'%(v[0],v[1],v[2]) txt += '\n\nEnd of Keyframe Data\n' # keyframe txt footer blend = Path(bpy.data.filepath) keyfile = blend.parent / 'render' / f'pos_{name}.txt' keyfile.parent.mkdir(parents=False, exist_ok=True) print(f'exporting keys for {name}') with open(keyfile, 'w') as fd: fd.write(txt) 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"} 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 += '\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): 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.co.y = k.co.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.co.y = k.co.y * ratio_y else: if cam.shift_y != 1: cam.shift_y = cam.shift_y * ratio_y return {"FINISHED"} # ui panel class GPEXP_PT_extra_gprender_func(bpy.types.Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "output" bl_label = "GP Render Extras" bl_parent_id = "RENDER_PT_format" if (3, 0, 0) <= bpy.app.version else "RENDER_PT_dimensions" bl_options = {'DEFAULT_CLOSED'} # COMPAT_ENGINES = {'CYCLES', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} # def draw_header(self, context): # overscan = context.scene.camera_overscan # self.layout.prop(overscan, "RO_Activate", text="") def draw(self, context): layout = self.layout col = layout.column() col.operator("gp.fix_overscan_shift") col.operator("gp.export_keys_to_ae") col.operator("gp.export_cam_keys_to_ae") # 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_export_cam_keys_to_ae, GPEXP_OT_fix_overscan_shift, GPEXP_PT_extra_gprender_func ) 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)