In a small part of a much larger repository of scripts and sequence of related .blend
files, I use bpy_extras.object_utils.world_to_camera_view
repeatedly, invoked like:
cam_coord_vec = world_to_camera_view(bpy.context.scene, cam, obj.matrix_world @ final_co)
Unfortunately, I'm seeing some non-determinism. In a print statement like the following (where v
is a mathutils.Vector()
), everything (at least, all non-callables; I have not yet called and checked the output of each callable in the final dir
) is identical except cam_coord_vec
:
print(f">>>> {v.co} # {v.index} -- {final_co} -- {obj.matrix_world @ final_co} -- {cam_coord_vec}")
print(f"#2>>>> \t {cam.location} {cam.data.shift_x} {cam.data.shift_y} {cam.data.ortho_scale} {cam.rotation_euler}")
print(f"#3>>>> \t {cam.data.sensor_height} {cam.data.sensor_width} {cam.data.clip_start} {cam.data.clip_end}")
print(f"#4>>>> \t {cam.data.display_size} {cam.data.lens}")
print(f"#5>>> \t {[getattr(cam.data, d) for d in dir(cam.data)]}")
print(f"#6>>> \t {bpy.context.scene.name}")
There is quite a lot going on in both the code and in the .blend
files -- e.g., many places where the code might trigger a scene update, liberal use of shapekeys driven by custom property drivers, and I'm developing in an old Blender version, v2.83.2. Maybe as a side effect of this, I'm struggling to isolate the cause, or to build a minimal working example of the problem.
The world_to_camera_view_docs say it "Takes shift-x/y, lens angle and sensor size into account as well as perspective/ortho projections.", but it's a bit unclear to me how to check that all of this data is identical between the two runs, or if I've covered all relevant items above.
Here's an example of the output of the above, from a first run:
#~ >>>> <Vector (1.5000, 0.5000, 0.0500)> # 0 -- <Vector (1.5000, 0.5000, 0.9775)> -- <Vector (0.5000, 0.5000, 0.9766)> -- <Vector (8.2782, -4.0987, 19.8199)>
#~ #2>>>> <Vector (2.6106, -12.6106, 18.2431)> 0.0 0.0 1.0 <Euler (x=0.7854, y=0.0000, z=0.7854), order='XYZ'>
#~ #3>>>> 24.0 36.0 0.10000000149011612 1000.0
#~ #4>>>> 1.0 50.0
#~ #5>>> [None, 'bpy.types', (), 0.6911112070083618, 0.6911112070083618, 0.4710899591445923, None, <bpy_func Camera.animation_data_clear()>, <bpy_func Camera.animation_data_create()>, bpy.data.cameras['Camera'].background_images, <bpy_struct, Struct("Camera")>, 1000.0, 0.10000000149011612, <bpy_func Camera.copy()>, bpy.data.cameras['Camera'].cycles, 1.0, bpy.data.cameras['Camera'].dof, <bpy_func Camera.evaluated_get()>, False, False, False, 50.0, 'MILLIMETERS', None, <bpy_func Camera.make_local()>, 'Camera', 'Camera', bpy.data.cameras['Camera'], 1.0, <bpy_func Camera.override_create()>, None, 0.5, None, bpy.data.cameras['Camera']...Struct, 'AUTO', 24.0, 36.0, 0.0, 0.0, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, bpy.data.cameras['Camera'].stereo, False, 'ORTHO', <bpy_func Camera.update_tag()>, False, <bpy_func Camera.user_clear()>, <bpy_func Camera.user_of_id()>, <bpy_func Camera.user_remap()>, 1, <bpy_func Camera.view_frame()>]
#~ #6>>> Scene
And from a second:
#~ >>>> <Vector (1.5000, 0.5000, 0.0500)> # 0 -- <Vector (1.5000, 0.5000, 0.9775)> -- <Vector (0.5000, 0.5000, 0.9766)> -- <Vector (-2.3284, 1.3752, 19.8199)>
#~ #2>>>> <Vector (2.6106, -12.6106, 18.2431)> 0.0 0.0 1.0 <Euler (x=0.7854, y=0.0000, z=0.7854), order='XYZ'>
#~ #3>>>> 24.0 36.0 0.10000000149011612 1000.0
#~ #4>>>> 1.0 50.0
#~ #5>>> [None, 'bpy.types', (), 0.6911112070083618, 0.6911112070083618, 0.4710899591445923, None, <bpy_func Camera.animation_data_clear()>, <bpy_func Camera.animation_data_create()>, bpy.data.cameras['Camera'].background_images, <bpy_struct, Struct("Camera")>, 1000.0, 0.10000000149011612, <bpy_func Camera.copy()>, bpy.data.cameras['Camera'].cycles, 1.0, bpy.data.cameras['Camera'].dof, <bpy_func Camera.evaluated_get()>, False, False, False, 50.0, 'MILLIMETERS', None, <bpy_func Camera.make_local()>, 'Camera', 'Camera', bpy.data.cameras['Camera'], 1.0, <bpy_func Camera.override_create()>, None, 0.5, None, bpy.data.cameras['Camera']...Struct, 'AUTO', 24.0, 36.0, 0.0, 0.0, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, bpy.data.cameras['Camera'].stereo, False, 'ORTHO', <bpy_func Camera.update_tag()>, False, <bpy_func Camera.user_clear()>, <bpy_func Camera.user_of_id()>, <bpy_func Camera.user_remap()>, 1, <bpy_func Camera.view_frame()>]
#~ #6>>> Scene
The two appear to be identical, except that the returned world_to_camera_view
vector is <Vector (8.2782, -4.0987, 19.8199)>
in the first case, and <Vector (-2.3284, 1.3752, 19.8199)>
in the second.
Are there other inputs world_to_camera_view
depends on that I could print, to look for the cause of this inconsistency?