4
$\begingroup$

... by which I mean the vectors from a perspective camera's viewing point to the corners of the rendered rectangle, as displayed by the diagonal sides of a camera's rectangular pyramid in a 3D View. (Normalized if you like, in Camera Space, if you like.)

The angle and view_frame attributes of a Camera don't seem to carry this information, unless I'm mistaken?

There are quite a few answers on how to test whether a point is visible to a camera, that's not quite the same thing.

Better still, and perhaps of more general use to others, too, would be a diagram of how bpy refers to a camera: what a Camera's important attributes actually mean, in 3D / device / screen space.

EDIT: following @batFINGER's examples (I think?)..trying to get the render frame, I run this:

import bpy

scn = bpy.context.scene

rx = scn.render.resolution_x
ry = scn.render.resolution_y
cam_obj = scn.camera

if (cam_obj.name in bpy.data.cameras): 
          
    cam_mw = cam_obj.matrix_world
    cam = cam_obj.data
    vf = cam.view_frame()
    world_frame = [cam_mw @ v for v in vf]
    
    print (rx,ry)
    for v in world_frame:
        print (v)  

And, changing the render resolutions, visibly changing the shape of the frustum in the 3D view, I get these outputs:

1512 1371
<Vector (2.1208, -6.3833, 4.1026)>
<Vector (2.3175, -6.8641, 3.2482)>
<Vector (1.3703, -7.1821, 3.2090)>
<Vector (1.1736, -6.7013, 4.0635)>

427 1432
<Vector (2.1208, -6.3833, 4.1026)>
<Vector (2.3175, -6.8641, 3.2482)>
<Vector (1.3703, -7.1821, 3.2090)>
<Vector (1.1736, -6.7013, 4.0635)>

Which is puzzling me. There's no change in the frame as a consequence of changing the proportions of the render - (the change isn't even hidden in the camera's world transform..)

SOLUTION: the camera's .view_frame() method needs its optional named scene parameter to update.. until I know why, I don't fancy making that an answer..

$\endgroup$
7
  • $\begingroup$ So you mean that four lines initial from camera origin which display as the camera Gizmos? $\endgroup$ – HikariTW Jul 21 '20 at 9:08
  • $\begingroup$ @HikariTW Those are the ones .. the length doesn't matter, just the vectors.. or any other way of getting the bounds of the field of view : the rendered rectangle $\endgroup$ – Robin Betts Jul 21 '20 at 10:03
  • $\begingroup$ Related blender.stackexchange.com/questions/6377/… also blender.stackexchange.com/questions/45146/… (used in that recent cow shed q your answer to which is great btw) and blender.stackexchange.com/a/160388/15543 $\endgroup$ – batFINGER Jul 21 '20 at 10:32
  • $\begingroup$ @batFINGER Thank you! I got those answers of yours, even UV'd and soaked them up enough to write the script around the stripped-down example above, (couldn't find them again to mention here) but I'm still stumped.. see edit.. I must be misunderstanding something obvious.. $\endgroup$ – Robin Betts Jul 21 '20 at 13:00
  • $\begingroup$ @batFINGER OK, got it. The named scene parameter to view_frame() for some reason kicks an update? Unless you know better why it should make a difference? Hesitate to answer my own q until I know what's going on. Maybe you do. $\endgroup$ – Robin Betts Jul 21 '20 at 13:25
5
$\begingroup$

Pass the scene to view frame

Think you got there in the end, here is @ideasman42's script modified to add empties to 5 points of frustum, for each camera object in scene.

All the info re resolution will come with the passed as keyword scene, else it defaults to None.

>>> C.scene.camera.data.view_frame(
view_frame()
Camera.view_frame(scene=None)
Return 4 points for the cameras frame (before object transformation)

Test script:

import bpy


def camera_as_planes(scene, obj):
    """
    Return planes in world-space which represent the camera view bounds.
    """
    from mathutils.geometry import normal
    print(obj.name)
    camera = obj.data
    # normalize to ignore camera scale
    matrix = obj.matrix_world.normalized()
    frame = [matrix @ v for v in camera.view_frame(scene=scene)]
    origin = matrix.to_translation()
    frame.append(origin)
    for p in frame:
        bpy.ops.object.empty_add(location=p)

# test call
scene = bpy.context.scene
cams = [o for o in scene.objects if o.type == 'CAMERA']
for cam in cams:
    camera_as_planes(scene, cam)
$\endgroup$
3
  • $\begingroup$ Thanks!... seems odd to have to pass an unchanged scene, to me ... and what would none mean to this method, if not a live ref. to the camera's context.scene? I have to learn more about what context is.. when it's about, when it isn't. Cheers. $\endgroup$ – Robin Betts Jul 21 '20 at 15:18
  • 1
    $\begingroup$ I'm assuming None uses the default values for resolution etc. Doesn't need to be context scene, simply the scene containing the camera. The camera data has no idea what scene it is in (could be in none or all) This data is required to make the calc to match the scene. Used context in example above to add empties. To simply print out could for scene in bpy.data.scenes: $\endgroup$ – batFINGER Jul 21 '20 at 15:24
  • $\begingroup$ Okydoky, beginning to get it... :) $\endgroup$ – Robin Betts Jul 21 '20 at 15:37

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.