8
$\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$
  • $\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$ – linuxhackerman Jan 19 '14 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$ – Sergey Krumas Jan 19 '14 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$ – linuxhackerman Jan 19 '14 at 16:31
  • $\begingroup$ Same thoughts, but it's too hard. Need to do a commit later. $\endgroup$ – Sergey Krumas Jan 19 '14 at 16:37
  • 1
    $\begingroup$ Before getting the coordinates I can check area.spaces[0].region3d.view_perspective == 'CAMERA'. It works cool =) $\endgroup$ – Sergey Krumas Jan 20 '14 at 11:02
6
$\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)

    # 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$
  • $\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$ – Sergey Krumas Jul 5 '17 at 8:33
  • $\begingroup$ @sergey-krumas good point, done! $\endgroup$ – ideasman42 Jul 5 '17 at 22:19
4
$\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$
  • $\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 '14 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$ – pink vertex Jan 29 '14 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$ – pink vertex Jan 29 '14 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 '14 at 15:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.