9
$\begingroup$

How to get coordinates of corners of camera border in camera view relative to the viewport areaenter image description here?

After checking

import bpy
print('-----')
print()
print()
for a in bpy.context.screen.areas:
    if a.type == 'VIEW_3D':
        for s in a.spaces:
            print(s)
            for d in dir(s):
                print(d)
print('-----')

I have no idea how to find out where I can get this

$\endgroup$
12
  • $\begingroup$ Rather than using that unwieldy python script, you can explore the structure using the "Datablocks" mode of the Outliner. I'm looking around to see if I can find a way to actually get the coordinates :) $\endgroup$ Jan 19, 2014 at 16:21
  • 1
    $\begingroup$ Yes, I know about Outliner, but Outliner is for datablocks, and what I wanna get is not store in datablocks at all. 100 % information =) $\endgroup$ Jan 19, 2014 at 16:24
  • $\begingroup$ I'm not so sure it's possible to get it directly, you'd probably have to calculate it from the zoom and the camera's attributes... $\endgroup$ Jan 19, 2014 at 16:31
  • $\begingroup$ Same thoughts, but it's too hard. Need to do a commit later. $\endgroup$ Jan 19, 2014 at 16:37
  • 1
    $\begingroup$ Before getting the coordinates I can check area.spaces[0].region3d.view_perspective == 'CAMERA'. It works cool =) $\endgroup$ Jan 20, 2014 at 11:02

2 Answers 2

8
$\begingroup$

There are 2 things to consider here:

  • The camera frame. The camera frame is not as simple as you might expect since its effected by the field-of-view, aspect & x/y shift.
  • The 2D view pixel coords, The user can pan & zoom the view, so this also has to be calculated.

This script gets the camera bounds and prints the pixel boundaries.

import bpy

def view3d_find():
    # returns first 3d view, normally we get from context
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            v3d = area.spaces[0]
            rv3d = v3d.region_3d
            for region in area.regions:
                if region.type == 'WINDOW':
                    return region, rv3d
    return None, None

def view3d_camera_border(scene):
    obj = scene.camera
    cam = obj.data

    frame = cam.view_frame(scene=scene)

    # move from object-space into world-space 
    frame = [obj.matrix_world @ v for v in frame]

    # move into pixelspace
    from bpy_extras.view3d_utils import location_3d_to_region_2d
    region, rv3d = view3d_find()
    frame_px = [location_3d_to_region_2d(region, rv3d, v) for v in frame]
    return frame_px

frame_px = view3d_camera_border(bpy.context.scene)
print("Camera frame:", frame_px)

See API docs for the important functions used here:

$\endgroup$
4
  • $\begingroup$ Thanks, that's what I actually needed! Your comment "# move into object space" confused me. Maybe the comment should be like "moving into world space"? Because view_frame function gives the positions relatively to object space of the camera, and for getting pixelspace coordinates we must provide positions of the points in world space, don't we? $\endgroup$ Jul 5, 2017 at 8:33
  • $\begingroup$ @sergey-krumas good point, done! $\endgroup$
    – ideasman42
    Jul 5, 2017 at 22:19
  • $\begingroup$ 2.8x API changes will require a small change: frame = cam.view_frame(scene=scene) $\endgroup$
    – chafouin
    Jul 3, 2020 at 23:22
  • $\begingroup$ Thanks, updated. $\endgroup$
    – ideasman42
    Jul 4, 2020 at 8:58
5
$\begingroup$

Workaround example

Add a plane to the scene, to see the effect:

import bpy
from mathutils import Matrix
from bpy_extras import view3d_utils

mesh = bpy.data.objects['Plane'].data
camera = bpy.data.objects['Camera']
data = camera.data

frame = data.view_frame()
render = bpy.context.scene.render
ar = render.resolution_y / render.resolution_x

mesh.vertices[0].co = frame[0]
mesh.vertices[1].co = frame[1]
mesh.vertices[2].co = frame[3]
mesh.vertices[3].co = frame[2]

scale = Matrix.Scale(ar, 4, (0.0,1.0,0.0))
mat = camera.matrix_world

mesh.transform(mat*scale)
mesh.update()

for area in bpy.context.screen.areas:
    if area.type=='VIEW_3D':
        break

space = area.spaces[0]
region = area.regions[4]

points_on_screen = [
    view3d_utils.location_3d_to_region_2d(
        region,
        space.region_3d,
        v.co
        )
    for v in mesh.vertices
    ]

print(*points_on_screen, sep="\n")

You can apply the matrix transform directly to the vectors in camera.view_frame and use location_3d_to_region_2d to get the screen coordinates.

The plane is used for visualization.

BTW: To get the W-component you have to expand the vectors before multiplication

v = Vector((0.0, 0.0, 0.0))
v.to_4d()
# Vector((0.0, 0.0, 0.0, 1.0))

space_data.region_3d.perspective_matrix seems to be already multiplied with the view matrix. You can reverse it like this:

perspective_matrix * view_matrix.inverted()

So

ndc = [None] * 4
for i, v in enumerate(camera.view_frame()):
    ndc[i] = perspective_matrix * matrix_world * scale * v.to_4d()
    ndc[i] /= ndc[i][3]

should give you the NDC-coordinates

$\endgroup$
4
  • $\begingroup$ Awesome pink vertex! This is exactly the calculation! I turned it into a modal draw op to visulize the result without a plane, fixed a problem with aspect ration > 1.0 and added a pink vertex in each corner =D It does not take Shift Y into account however, any idea how to solve this? pasteall.org/49117/python (Run Script, spacebar menu over 3D View, "modal", switch to camera view with Numpad 0! $\endgroup$
    – CodeManX
    Jan 28, 2014 at 22:00
  • $\begingroup$ Shift Y screws up the scaling because (0.0,0.0) is not the center anymore. You can fix this by a translation. $\endgroup$ Jan 29, 2014 at 9:13
  • 1
    $\begingroup$ center=sum(camera.view_frame(),Vector((0.0,0.0,0.0)))/4 Create a translation matrix tmat=Matrix.Translation(Vector((0.0,-center[1],0.0)) And multiply it like this tmat.inverted()*scale*tmat You can simplify this by computing it by hand and set the entry in the scale matrix directly. $\endgroup$ Jan 29, 2014 at 9:28
  • $\begingroup$ Great! Integrated the manual way into my script and accounted for aspect ration > 1.0 again: pasteall.org/49139/python Note that you need to be in Camera perspective already if you want to use the get points function standalone (or it won't return the correct locations). $\endgroup$
    – CodeManX
    Jan 29, 2014 at 15:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .