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 from bpy.types import GizmoGroup, Gizmo 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'} # GIZMO method ''' class CameraFrameWidget(Gizmo): bl_idname = "VIEW3D_GT_CAM" # bl_target_properties = ( # {"id": "offset", "type": 'FLOAT', "array_length": 1}, # ) # __slots__ = ( # "custom_shape", # "init_mouse_y", # "init_value", # ) # def _update_offset_matrix(self): # # offset behind the light # self.matrix_offset.col[3][2] = self.target_get_value("offset") / -10.0 def draw(self, context): # self._update_offset_matrix() # self.draw_custom_shape(self.custom_shape) osd_color = (0.06, 0.4, 0.040, 0.4) frame_point = view3d_camera_border_3d(context, context.scene.camera.parent) self.shader_2d = gpu.shader.from_builtin('3D_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) # def draw_select(self, context, select_id): # self._update_offset_matrix() # self.draw_custom_shape(self.custom_shape, select_id=select_id) # def setup(self): # if not hasattr(self, "custom_shape"): # self.custom_shape = self.new_custom_shape('LINE_LOOP', custom_shape_verts) def invoke(self, context, event): # self.init_mouse_y = event.mouse_y # self.init_value = self.target_get_value("offset") return {'RUNNING_MODAL'} def exit(self, context, cancel): context.area.header_text_set(None) # if cancel: # self.target_set_value("offset", self.init_value) def modal(self, context, event, tweak): # context.area.header_text_set("My Gizmo: %.4f" % value) return {'RUNNING_MODAL'} class CameraFrameWidgetGroup(GizmoGroup): bl_idname = "OBJECT_GGT_light_test" bl_label = "Test Light Widget" bl_space_type = 'VIEW_3D' bl_region_type = 'WINDOW' bl_options = {'3D', 'PERSISTENT'} @classmethod def poll(cls, context): # if context.region_data.view_perspective != 'CAMERA': # return # if context.scene.camera.name != 'draw_cam': # return # if not context.scene.camera.parent: # return # return True return context.region_data.view_perspective == 'CAMERA' and context.scene.camera.name == 'draw_cam' and context.scene.camera.parent def setup(self, context): # Assign the 'offset' target property to the light energy. ob = context.object gz = self.gizmos.new(CameraFrameWidget.bl_idname) print('In setup') # gz.target_set_prop("offset", ob.data, "energy") # gz.color = 1.0, 0.5, 1.0 # gz.alpha = 0.5 # gz.color_highlight = 1.0, 1.0, 1.0 # gz.alpha_highlight = 0.5 # units are large, so shrink to something more reasonable. # gz.scale_basis = 0.1 gz.use_draw_modal = True def refresh(self, context): ob = context.object0 # gz = self.energy_gizmo # gz.matrix_basis = ob.matrix_world.normalized() ''' # USED 0CODE def extrapolate_points_by_length(a, b, length): ''' Return a third point C from by continuing in AB direction Length define BC distance. both vector2 and vector3 ''' # return b + ((b - a).normalized() * length)# one shot ab = b - a if not ab: return None return b + (ab.normalized() * length) def view3d_camera_border_3d(context, cam): '''Return frame border as 3D coordinate''' frame = cam.data.view_frame(scene=context.scene) frame = [cam.matrix_world @ v for v in frame] return frame class CameraFrameWidget(Gizmo): bl_idname = "VIEW3D_GT_CAM" def draw(self, context): # 3D0 draw osd_color = (0.06, 0.4, 0.040, 0.4) frame_point = view3d_camera_border_3d( context, context.scene.camera.parent) self.shader_2d = gpu.shader.from_builtin('3D_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) # WIP -> custom passepartout ## frame positions # D-----A # | | # C-----B # a = frame_point[0] # b = frame_point[1] # c = frame_point[2] # d = frame_point[3] # ext = 0.2 # rup = extrapolate_points_by_length(a,b, ext) # rdn = extrapolate_points_by_length(b,a, ext) # rupext = rup + ((a-c).normalized() * ext) # rdnext = rdn + ((a-c).normalized() * ext) # rect = [rup, rdn, rupext, rdnext] # ### passpartout_points = [] # self.passepartout = batch_for_shader( # self.shader_2d, 'TRI_FAN', {"pos": rect}) # TRIS # self.shader_2d.bind() # self.shader_2d.uniform_float("color", osd_color) # self.passepartout.draw(self.shader_2d) # def invoke(self, context, event): # return {'RUNNING_MODAL'} # def exit(self, context, cancel): # # context.area.header_text_set(None) # return # def modal(self, context, event, tweak): # return {'RUNNING_MODAL'} class CameraFrameWidgetGroup(GizmoGroup): bl_idname = "OBJECT_GGT_light_test" bl_label = "Test Light Widget" bl_space_type = 'VIEW_3D' bl_region_type = 'WINDOW' bl_options = {'3D', 'PERSISTENT'} @classmethod def poll(cls, context): return context.region_data.view_perspective == 'CAMERA' and context.scene.camera.name == 'draw_cam' and context.scene.camera.parent def setup(self, context): gz = self.gizmos.new(CameraFrameWidget.bl_idname) # --- REGISTER --- classes = ( # GPTB_OT_cam_frame_draw, CameraFrameWidget, CameraFrameWidgetGroup, ) def register(): if not bpy.app.background: for cls in classes: bpy.utils.register_class(cls) # ha = DrawClass() # _handle = bpy.types.SpaceView3D.draw_handler_add( # draw_cam_frame_callback, args, "WINDOW", "POST_PIXEL") def unregister(): if not bpy.app.background: for cls in classes: bpy.utils.unregister_class(cls) # 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()