import bpy import gpu import bgl # import blf from gpu_extras.batch import batch_for_shader from bpy_extras.view3d_utils import location_3d_to_region_2d from bpy.app.handlers import persistent def view3d_camera_border(context, cam): ## based on https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border # cam = context.scene.camera frame = cam.data.view_frame(scene=context.scene) # to world-space frame = [cam.matrix_world @ v for v in frame] # to pixelspace region, rv3d = context.region, context.space_data.region_3d frame_px = [location_3d_to_region_2d(region, rv3d, v) for v in frame] return frame_px def draw_cam_frame_callback(self, context): if context.region_data.view_perspective != 'CAMERA': return if context.scene.camera.name != 'draw_cam': return main_cam = context.scene.camera.parent if not main_cam: return # if context.area != self._draw_area: # return # green -> (0.06, 0.4, 0.040, 0.6) # orange -> (0.45, 0.18, 0.03, 1.0) osd_color = (0.06, 0.4, 0.040, 0.4) frame_point = view3d_camera_border(context, main_cam) self.shader_2d = gpu.shader.from_builtin('2D_UNIFORM_COLOR') self.screen_framing = batch_for_shader( self.shader_2d, 'LINE_LOOP', {"pos": frame_point}) bgl.glLineWidth(1) self.shader_2d.bind() self.shader_2d.uniform_float("color", osd_color) self.screen_framing.draw(self.shader_2d) # Reset # bgl.glLineWidth(1) # # Display Text # if self.use_osd_text: # font_id = 0 # dpi = context.preferences.system.dpi # # Display current frame text # blf.color(font_id, *osd_color) # unpack color in argument # blf.position(font_id, context.region.width/3, 15, 0) # context.region.height- # blf.size(font_id, 16, dpi) # blf.draw(font_id, f'Draw cam') ## As a modal for tests class GPTB_OT_cam_frame_draw(bpy.types.Operator): bl_idname = "gp.draw_cam_frame" bl_label = "Draw Cam Frame" bl_description = "Draw the camera frame using a modal" bl_options = {"REGISTER"} # , "INTERNAL" # @classmethod # def poll(cls, context): # return True def invoke(self, context, event): ## screen color frame # r = bpy.context.region # w = r.width # h = r.height # self.shader_2d = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # self.screen_framing = batch_for_shader( # self.shader_2d, 'LINE_LOOP', {"pos": [(0,0), (0,h), (w,h), (w,0)]}) ## OpenGL handler self._draw_area = context.area args = (self, context) self._handle = bpy.types.SpaceView3D.draw_handler_add( draw_cam_frame_callback, args, "WINDOW", "POST_PIXEL") context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} def exit(self, context): bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') context.area.tag_redraw() def modal(self, context, event): if event.type in {'ESC'}: self.exit(context) return {"CANCELLED"} return {'PASS_THROUGH'} # return {'RUNNING_MODAL'} class DrawClass: def __init__(self, context): # print("INIT") self._handle = bpy.types.SpaceView3D.draw_handler_add( draw_cam_frame_callback, args, "WINDOW", "POST_PIXEL") def remove_handle(self): bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') ### --- REGISTER --- def register(): if not bpy.app.background: bpy.utils.register_class(GPTB_OT_cam_frame_draw) # ha = DrawClass() # _handle = bpy.types.SpaceView3D.draw_handler_add( # draw_cam_frame_callback, args, "WINDOW", "POST_PIXEL") def unregister(): if not bpy.app.background: bpy.utils.unregister_class(GPTB_OT_cam_frame_draw) # ha.remove_handle() # bpy.types.SpaceView3D.draw_handler_remove(_handle, 'WINDOW') # if 'draw_cam_frame_callback' in [hand.__name__ for hand in bpy.app.handlers.save_pre]: # bpy.app.handlers.save_pre.remove(remap_relative) if __name__ == "__main__": register()