0

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

oscr104
  • 35
  • 5
  • Hello ! Shouldn't cam_obj.rotation_euler = (0, 0, -1) be cam_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:02
  • No I didnt use chatGPT, why do you ask? The initial rotation is (0, 0, -1) so that the camera points directly downwards to begin with, as explained in the text – oscr104 May 24 '23 at 13:13
  • Ah I see, you're correct that (0, 0, 0) would be neutral orientation. The angle relative to the z axis is more important than the z rotation, so I hadn't picked up on this. Edited to reflect this. – oscr104 May 24 '23 at 13:20
  • Because the syntax of the code looks as an output out of chatGPT, and having this information may be helpful in solving your problem. Have you tested it in the 3D viewport ? A rotation of -1 radians is approximately -57,3° around the Z axis;, so it does point downwards but at a random angle – Gorgious May 24 '23 at 13:20
  • You should convert all your angles to radians using math.radians, trigonometric operations use radians instead of degrees – Gorgious May 24 '23 at 13:22
  • Yes, I'm developing the script in the GUI, once the script works it will need to run headless in background mode (so i cant be setting active objects using mouse etc) – oscr104 May 24 '23 at 13:24
  • also, it doesn't serve any purpose to reset the roation because it is overwritten by the last line of the function – Gorgious May 24 '23 at 13:24
  • Are you sure about the order of operations ? If you apply the translation, THEN the rotation, the pivot of your rotation will be at the center of the world, so you object will be sent flying away. If you invert R.to_4x4() @ T to T @ R.to_4x4() the rotation is applied with the same pivot point, THEN translated. – Gorgious May 24 '23 at 13:27
  • But I am using math.radians to convert, unless I have missed something?

    The 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:27
  • The order of operations is based on Jaroslavs answer to this problem

    https://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

0 Answers0