import bpy from mathutils import Vector class Rect: def __init__(self, x, y, width, height): self.x = x self.y = y self.width = width self.height = height @property def top_left(self): return Vector((self.x, self.y)) @property def bottom_left(self): return Vector((self.x, self.y - self.height)) @property def bottom_right(self): return Vector((self.x + self.width, self.y - self.height)) @property def top_right(self): return Vector((self.x + self.width, self.y)) def __str__(self): return f'Rect(x={self.x}, y={self.y}, width={self.width}, height={self.height})' class View3D: def __init__(self, area=None): if area is None: area = bpy.context.area if area.type != 'VIEW_3D': area = next((area for area in bpy.context.screen.areas if area.type == 'VIEW_3D'), None) self.area = area @property def sidebar(self): return self.area.regions[3] @property def toolbar(self): return self.area.regions[2] @property def tool_header(self): return self.area.regions[1] @property def header(self): return self.area.regions[0] @property def space(self): return self.area.spaces.active @property def region(self): return self.area.regions[5] @property def region_3d(self): return self.space.region_3d @property def x(self): return 0 @property def y(self): return 0 @property def width(self): return self.region.width @property def height(self): return self.region.height @property def rect(self): return Rect(self.x, self.y, self.width, self.height) @property def reduced_rect(self): w, h = self.region.width, self.region.height if not bpy.context.preferences.system.use_region_overlap: return self.rect ## Minus tool leftbar + sidebar right # top_margin = bottom_margin = 0 # if self.tool_header.alignment == 'TOP': # top_margin += self.tool_header.height # else: # bottom_margin += self.tool_header.height ## Set corner values # top_left = (self.toolbar.width, h - top_margin - 1) # top_right = (w - self.sidebar.width, h - top_margin - 1) # bottom_right = (w - self.sidebar.width, bottom_margin + 2) # bottom_left = (self.toolbar.width, bottom_margin + 2) reduced_y = 0 if self.tool_header.alignment == 'TOP': reduced_y = self.tool_header.height reduced_width = w - self.sidebar.width - self.toolbar.width reduced_height = h - self.tool_header.height - 1 return Rect(self.toolbar.width, h - reduced_y - 1, reduced_width, reduced_height) # return Rect(self.toolbar.width, h - reduced_y - 1, right_down, left_down) def to_2d(self, coord): from bpy_extras.view3d_utils import location_3d_to_region_2d return location_3d_to_region_2d(self.region, self.region_3d, coord) def to_3d(self, coord, depth_coord=None): from bpy_extras.view3d_utils import region_2d_to_location_3d if depth_coord is None: depth_coord = bpy.context.scene.cursor.location return region_2d_to_location_3d(self.region, self.region_3d, coord, depth_coord) def get_camera_frame_3d(self, scene=None, camera=None): if scene is None: scene = bpy.context.scene if camera is None: camera = scene.camera frame = camera.data.view_frame() mat = camera.matrix_world return [mat @ v for v in frame] def get_camera_frame_2d(self, scene=None, camera=None): '''View frame Top_right-CW''' if scene is None: scene = bpy.context.scene frame_3d = self.get_camera_frame_3d(scene=scene, camera=camera) frame_2d = [self.to_2d(v) for v in frame_3d] rd = scene.render resolution_x = rd.resolution_x * rd.pixel_aspect_x resolution_y = rd.resolution_y * rd.pixel_aspect_y ratio_x = min(resolution_x / resolution_y, 1.0) ratio_y = min(resolution_y / resolution_x, 1.0) ## Top right - CW frame_width = (frame_2d[1].x - frame_2d[2].x) # same size (square) frame_height = (frame_2d[0].y - frame_2d[1].y) # same size (square) cam_width = (frame_2d[1].x - frame_2d[2].x) * ratio_x cam_height = (frame_2d[0].y - frame_2d[1].y) * ratio_y cam_x = frame_2d[3].x - ((frame_width - cam_width) / 2) cam_y = frame_2d[3].y - ((frame_height - cam_height) / 2) return Rect(cam_x, cam_y, cam_width, cam_height) def zoom_from_fac(self, zoomfac): from math import sqrt return (sqrt(4 * zoomfac) - sqrt(2)) * 50.0 def fit_camera_view(self): ## CENTER self.region_3d.view_camera_offset = (0,0) ## ZOOM # rect = self.reduced_rect rect = self.rect cam_frame = self.get_camera_frame_2d() # print('width: ', rect.width) # print('height: ', rect.height) # xfac = rect.width / (cam_frame.width + 4) # yfac = rect.height / (cam_frame.height + 4) # # xfac = rect.width / (rect.width - 4) # # yfac = rect.height / (rect.height - 4) scene = bpy.context.scene rd = scene.render # resolution_x = rd.resolution_x * rd.pixel_aspect_x # resolution_y = rd.resolution_y * rd.pixel_aspect_y # xfac = min(resolution_x / resolution_y, 1.0) # yfac = min(resolution_y / resolution_x, 1.0) # xfac = rect.width / (cam_frame.width * rect.width + 4) # yfac = rect.height / (cam_frame.height * rect.height + 4) # xfac = rect.width / ((rect.width - cam_frame.width) * 2 + 4) # yfac = rect.height / (rect.height - cam_frame.height + 4) # xfac = rect.width / ((rect.width - cam_frame.width * rd.resolution_x) * 2 + 4) # xfac = rect.width / ((rect.width / cam_frame.width) * rd.resolution_x) # xfac = rect.width / rd.resolution_x * 2 # xfac = rect.width / ((cam_frame.width / rect.width) * rd.resolution_x) # xfac = self.region.width / (rect.width - cam_frame.width) # xfac = self.region.width / (rect.width - cam_frame.width) # xfac = ((cam_frame.width - rect.width) / rd.resolution_x) * self.region.width xfac = rect.width / cam_frame.width # xfac = 0.8 # xfac = yfac = 0.8 # xfac = yfac = 1.0 # print('xfac: ', xfac) # Dbg # print('yfac: ', yfac) # Dbg # fac = min([xfac, yfac]) # Dbg fac = xfac # fac = rd.resolution_x / self.width print('fac: ', fac) print('zoom before', self.region_3d.view_camera_zoom) # Dbg self.region_3d.view_camera_zoom = self.zoom_from_fac(fac) print('zoom after', self.region_3d.view_camera_zoom) # Dbg if __name__ == "__main__": ## construct view 3d class view3d = View3D() print(view3d.rect) print(view3d.rect.bottom_left) print(view3d.get_camera_frame_3d()) ## construct rect_frame = view3d.get_camera_frame_2d() view3d.reduced_rect.bottom_left bpy.context.scene.cursor.location = view3d.to_3d(view3d.reduced_rect.bottom_right)