I am writing a script to procedurally generate images of a target object on a terrain. The basic logic I am thinking of is to have the camera directly above the target pointing straight down, apply some XYZ offsets so that the target object is not exactly centred, then rotate the camera around the target (by a random amount from zero to a specified maximum rotation value, keeping the offsets, and keeping the direction of the camera relative to the target)
This has to be all in python scripting, as it will be running headless without the GUI. This function will be called multiple times to generate images, with the target object moving every time, so needs to reset the camera position & orientation, apply the offsets, then rotate.
Here is a dummy version of what I have so far, using the default cube & camera.
import bpy
from random import uniform
from mathutils import Matrix, Euler
import math
def add_jitter(base_value: int = None, jitter: int = None) -> int:
"""
helper function to add random jitter to camera position
"""
jittered_value = base_value + uniform(-jitter, jitter)
return jittered_value
def place_camera(
cam_obj: object = None,
cam_height: int = None,
xy_jitter: int = None,
height_jitter: int = None,
rotation_bound: int = None,
target: object = None,
) -> None:
# Reset camera orientation (straight down)
cam_obj.rotation_euler = (0, 0, 0)
# Add random jitter to xyz coordinates
height_jittered = add_jitter(target.location.z + cam_height, height_jitter)
x_jittered = add_jitter(target.location.x, xy_jitter)
y_jittered = add_jitter(target.location.y, xy_jitter)
# Generate random rotations to apply
theta_x = math.radians(uniform(0, rotation_bound))
theta_y = math.radians(uniform(0, rotation_bound))
theta_z = math.radians(uniform(0, 360))
# Generate translation & rotation matrices, apply sequentially to camera
T = Matrix.Translation((x_jittered, y_jittered, height_jittered))
R = Euler((theta_x, theta_y, theta_z), "XYZ").to_matrix()
mat = R.to_4x4() @ T
cam_obj.matrix_world = mat
cam_obj = bpy.context.scene.objects["Camera"]
target = bpy.context.scene.objects["Cube"]
camera_height = 10
xy_jitter = 2
height_jitter = 2
max_rotation = 45
place_camera(cam_obj, camera_height, xy_jitter, height_jitter, max_rotation, target)
However, this does not seem to do the translation & rotation around the target object as intended, I get strange camera placements and orientations that dont face the target. I am relatively new to scripting in blender, and very new to matrix transformations, so its very possible I'm applying my rotations & transform matrices incorrectly
cam_obj.rotation_euler = (0, 0, -1)becam_obj.rotation_euler = (0, 0, 0)? Are you by any chance using a generative language model like chatGPT to write your script ? – Gorgious May 24 '23 at 13:02math.radians, trigonometric operations use radians instead of degrees – Gorgious May 24 '23 at 13:22R.to_4x4() @ TtoT @ R.to_4x4()the rotation is applied with the same pivot point, THEN translated. – Gorgious May 24 '23 at 13:27The rotation is reset at the start of the function because when this function works, it will be part of a bigger script to repeatedly generate images from the same camera and target object, moving them around each time. So the camera should reset each time to a neutral position, correct?
What do mean about the syntax looking like chatGPT? Is there something wrong with it?
– oscr104 May 24 '23 at 13:27https://blender.stackexchange.com/questions/44760/rotate-objects-around-their-origin-along-a-global-axis-scripted-without-bpy-op
See the gif in his answer showing the difference between the orders of operations - for my purposes I think i need to do R.to_4x4() @ T (as per the right hand side of that gif) so that the relative orientation of the camera to the target object is preserved (IE the camera remains pointing toward the object)
– oscr104 May 24 '23 at 13:29