I'm attempting to write a script that will place an object on a "terrain" plane, within the FOV of an existing camera which will be somewhere above that plane, pointing downwards, but with the exact coordinates and view angle randomised each time the code runs. I have the following code, which should find the 4 coordinates on the terrain corresponding to the 4 corners of the camera FOV (by sending raycasts out along the 4 corners of the camera view and getting the locations where they hit the terrain), however it seems to get the 4 corners of the 3D viewport, not the camera (tested by placing primitive cubes at the 4 corner locations. Therefore, when the object is placed, it is not placed within the camera FOV, but within the bounds of the 3D viewport. (For further context, this script will be run in headless mode, so any solution has to be 100% using bpy, not the user interface).
This is the code finding the 4 coordinates of the camera frame on the terrain plane:
def camera_normalized_frame(
cam: object = None,
) -> List[Vector]:
render = bpy.context.scene.render
aspect = (render.resolution_x * render.pixel_aspect_x) / (
render.resolution_y * render.pixel_aspect_y
)
view_frame = cam.data.view_frame(scene=None)
frame = [-f / view_frame[0].z for f in view_frame]
if aspect > 1:
mat = Matrix.Diagonal(
(1, 1 / aspect, 1),
)
else:
mat = Matrix.Diagonal(
(aspect, 1, 1),
)
for i in range(len(frame)):
frame[i] = mat @ frame[i]
return frame
def place_visible_xy(
target: object = None,
terrain: object = None,
camera: object = None,
):
# Get camera frame to constrain raycast direction
frame = camera_normalized_frame(camera)
for v in frame:
hit, loc, _, _, _, _ = bpy.context.scene.ray_cast(
bpy.context.view_layer.depsgraph,
origin=camera.location,
direction=v,
distance=20000,
)
# Add primitives to test hit locations
bpy.ops.mesh.primitive_cube_add(size=100, location=loc)