From 59311c1ef1cbaacef1b1b5c226ef013db61329e6 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 9 Mar 2024 17:46:51 +0100 Subject: [PATCH 1/4] Create Cambrigde.py --- Cambrigde.py | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 Cambrigde.py diff --git a/Cambrigde.py b/Cambrigde.py new file mode 100644 index 0000000..8413b14 --- /dev/null +++ b/Cambrigde.py @@ -0,0 +1,136 @@ +import bpy +from mathutils import Vector + +import bpy +from mathutils import Vector + +def update_camera_focal_length(camera, target_object, frame, max_frame_coverage=0.7): + """ + Updates the camera's focal length to ensure the target object occupies a specified maximum + percentage of the frame. + + Args: + - camera: The camera object. + - target_object: The target object to focus on. + - frame: The current frame number. + - max_frame_coverage: The maximum percentage of the frame's width or height the object should occupy. + """ + # Switch to the given frame to calculate current positions + bpy.context.scene.frame_set(frame) + + # Calculate the dimensions of the target object's bounding box in world space + bbox_corners = [target_object.matrix_world @ Vector(corner) for corner in target_object.bound_box] + bbox_dimensions = Vector((max(corner.x for corner in bbox_corners) - min(corner.x for corner in bbox_corners), + max(corner.y for corner in bbox_corners) - min(corner.y for corner in bbox_corners), + max(corner.z for corner in bbox_corners) - min(corner.z for corner in bbox_corners))) + + # Calculate the distance from the camera to the target object + distance_to_target = (camera.location - target_object.location).length + + # Calculate the sensor width and height based on the camera's sensor fit + if camera.data.sensor_fit == 'HORIZONTAL': + sensor_width = camera.data.sensor_width + sensor_height = camera.data.sensor_width * camera.data.sensor_height / camera.data.sensor_width + else: + sensor_height = camera.data.sensor_height + sensor_width = camera.data.sensor_height * camera.data.sensor_width / camera.data.sensor_height + + # Determine the object's largest dimension and corresponding sensor dimension + largest_dimension = max(bbox_dimensions) + sensor_dimension = sensor_width if bbox_dimensions.x == largest_dimension or bbox_dimensions.y == largest_dimension else sensor_height + + # Calculate the desired coverage in Blender units on the sensor + desired_coverage = sensor_dimension * max_frame_coverage + + # Calculate the necessary focal length to achieve the desired object coverage + # Using similar triangles: (largest_dimension / 2) / distance_to_target = (desired_coverage / 2) / focal_length + new_focal_length = (desired_coverage / 2) * distance_to_target / (largest_dimension / 2) + + # Update the camera's focal length + camera.data.lens = new_focal_length + +def setup_camera_rig(curve_name, target_object_name, num_frames, fps, initial_focal_length=50, max_frame_coverage=0.7): + """ + Enhances the initial setup_camera_rig function with dynamic focal length adjustment + to keep the target object within a specified frame coverage. + + Additional Args: + - max_frame_coverage: The maximum percentage of the frame's width or height the target object should occupy. + """ + # Previous setup_camera_rig implementation... + + # Inside the loop that iterates through each frame, call update_camera_focal_length: + for frame in range(1, num_frames + 1): + # Existing operations to animate the camera... + + # Update the camera's focal length for the current frame + update_camera_focal_length(camera, target_object, frame, max_frame_coverage) + + # Keyframe the new focal length + camera.data.keyframe_insert(data_path="lens", frame=frame) + +# Example usage remains the same... + + +def setup_camera_rig(curve_name, target_object_name, num_frames, fps, initial_focal_length=50): + """ + Sets up a camera rig that moves along a Bézier curve focusing on a specified object. + + Args: + - curve_name (str): The name of the Bézier curve object. + - target_object_name (str): The name of the object to focus on. + - num_frames (int): The total number of frames for the animation. + - fps (int): The frames per second value for the animation. + - initial_focal_length (float): The initial focal length of the camera. + """ + + # Ensure the curve and target object exist + curve = bpy.data.objects.get(curve_name) + target_object = bpy.data.objects.get(target_object_name) + if not curve or not target_object: + print("Curve or target object does not exist.") + return + + # Create a camera if it doesn't exist + if "CameraRig" not in bpy.data.objects: + bpy.ops.object.camera_add() + camera = bpy.context.object + camera.name = "CameraRig" + else: + camera = bpy.data.objects["CameraRig"] + + # Set camera data properties + camera.data.lens = initial_focal_length + camera.data.sensor_fit = 'HORIZONTAL' + + # Set the animation start and end + bpy.context.scene.frame_start = 1 + bpy.context.scene.frame_end = num_frames + bpy.context.scene.render.fps = fps + + # Create a follow path constraint and attach the camera to the curve + follow_path_constraint = camera.constraints.new(type='FOLLOW_PATH') + follow_path_constraint.target = curve + follow_path_constraint.use_curve_follow = True + bpy.ops.constraint.followpath_path_animate(constraint="Follow Path", owner='OBJECT', frame_start=1, length=num_frames) + + # Make the camera look at the target object by adding a Track To constraint + track_to_constraint = camera.constraints.new(type='TRACK_TO') + track_to_constraint.target = target_object + track_to_constraint.track_axis = 'TRACK_NEGATIVE_Z' + track_to_constraint.up_axis = 'UP_Y' + + # Animate focal length and DOF distance based on the distance to the target object + for frame in range(1, num_frames + 1): + bpy.context.scene.frame_set(frame) + distance = (camera.location - target_object.location).length + camera.data.lens = initial_focal_length + distance * 0.1 # Example focal length adjustment + camera.data.dof.focus_distance = distance + + camera.data.keyframe_insert(data_path="lens", frame=frame) + camera.data.keyframe_insert(data_path="dof.focus_distance", frame=frame) + + print("Camera rig setup complete.") + +# Example usage +setup_camera_rig("BezierCurve", "Cube", num_frames=120, fps=24, initial_focal_length=35) From be1c6e079e1c1b5bb7b384e836fde7094d08a188 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 9 Mar 2024 18:02:49 +0100 Subject: [PATCH 2/4] Update and rename Cambrigde.py to camrig-old.py --- Cambrigde.py => camrig-old.py | 78 +++++++++++++++-------------------- 1 file changed, 34 insertions(+), 44 deletions(-) rename Cambrigde.py => camrig-old.py (64%) diff --git a/Cambrigde.py b/camrig-old.py similarity index 64% rename from Cambrigde.py rename to camrig-old.py index 8413b14..0cf95ce 100644 --- a/Cambrigde.py +++ b/camrig-old.py @@ -49,38 +49,16 @@ def update_camera_focal_length(camera, target_object, frame, max_frame_coverage= # Update the camera's focal length camera.data.lens = new_focal_length -def setup_camera_rig(curve_name, target_object_name, num_frames, fps, initial_focal_length=50, max_frame_coverage=0.7): +def setup_camera_rig(curve_name, target_object_name, initial_focal_length=50): """ - Enhances the initial setup_camera_rig function with dynamic focal length adjustment - to keep the target object within a specified frame coverage. - - Additional Args: - - max_frame_coverage: The maximum percentage of the frame's width or height the target object should occupy. - """ - # Previous setup_camera_rig implementation... - - # Inside the loop that iterates through each frame, call update_camera_focal_length: - for frame in range(1, num_frames + 1): - # Existing operations to animate the camera... - - # Update the camera's focal length for the current frame - update_camera_focal_length(camera, target_object, frame, max_frame_coverage) - - # Keyframe the new focal length - camera.data.keyframe_insert(data_path="lens", frame=frame) - -# Example usage remains the same... - - -def setup_camera_rig(curve_name, target_object_name, num_frames, fps, initial_focal_length=50): - """ - Sets up a camera rig that moves along a Bézier curve focusing on a specified object. + Sets up a camera rig that moves along a Bézier curve focusing on a specified object, + adjusting the camera's focal length to keep the target object occupying roughly 70% + of the frame width or height, depending on its orientation. The animation end frame + is taken from the current scene settings. Args: - curve_name (str): The name of the Bézier curve object. - target_object_name (str): The name of the object to focus on. - - num_frames (int): The total number of frames for the animation. - - fps (int): The frames per second value for the animation. - initial_focal_length (float): The initial focal length of the camera. """ @@ -91,7 +69,7 @@ def setup_camera_rig(curve_name, target_object_name, num_frames, fps, initial_fo print("Curve or target object does not exist.") return - # Create a camera if it doesn't exist + # Create or retrieve the camera if "CameraRig" not in bpy.data.objects: bpy.ops.object.camera_add() camera = bpy.context.object @@ -99,38 +77,50 @@ def setup_camera_rig(curve_name, target_object_name, num_frames, fps, initial_fo else: camera = bpy.data.objects["CameraRig"] - # Set camera data properties - camera.data.lens = initial_focal_length camera.data.sensor_fit = 'HORIZONTAL' - # Set the animation start and end - bpy.context.scene.frame_start = 1 - bpy.context.scene.frame_end = num_frames - bpy.context.scene.render.fps = fps + # Use scene's current frame range + start_frame = bpy.context.scene.frame_start + end_frame = bpy.context.scene.frame_end - # Create a follow path constraint and attach the camera to the curve follow_path_constraint = camera.constraints.new(type='FOLLOW_PATH') follow_path_constraint.target = curve follow_path_constraint.use_curve_follow = True - bpy.ops.constraint.followpath_path_animate(constraint="Follow Path", owner='OBJECT', frame_start=1, length=num_frames) + bpy.ops.constraint.followpath_path_animate(constraint="Follow Path", owner='OBJECT', frame_start=start_frame, length=end_frame - start_frame + 1) - # Make the camera look at the target object by adding a Track To constraint track_to_constraint = camera.constraints.new(type='TRACK_TO') track_to_constraint.target = target_object track_to_constraint.track_axis = 'TRACK_NEGATIVE_Z' track_to_constraint.up_axis = 'UP_Y' - # Animate focal length and DOF distance based on the distance to the target object - for frame in range(1, num_frames + 1): + # Calculate and animate the focal length + for frame in range(start_frame, end_frame + 1): bpy.context.scene.frame_set(frame) - distance = (camera.location - target_object.location).length - camera.data.lens = initial_focal_length + distance * 0.1 # Example focal length adjustment - camera.data.dof.focus_distance = distance + # Calculate distance to target object and adjust focal length to keep object within frame + camera_loc = camera.matrix_world.translation + target_loc = target_object.matrix_world.translation + direction = (target_loc - camera_loc).normalized() + distance = (target_loc - camera_loc).length + + # Calculate dimensions of the target object + dimensions = target_object.dimensions + max_dimension = max(dimensions.x, dimensions.y, dimensions.z) + + # Assuming a sensor width of 36mm (default in Blender) and frame aspect ratio to determine whether to use width or height + sensor_width = camera.data.sensor_width + aspect_ratio = bpy.context.scene.render.resolution_x / bpy.context.scene.render.resolution_y + frame_dimension = sensor_width if aspect_ratio >= 1 else sensor_width / aspect_ratio + + # Calculate focal length to fit the object within 70% of the frame's largest dimension + scale_factor = 0.7 # Target to occupy 70% of the frame + focal_length = (distance * initial_focal_length) / (max_dimension / frame_dimension * scale_factor) + camera.data.lens = focal_length + camera.data.keyframe_insert(data_path="lens", frame=frame) - camera.data.keyframe_insert(data_path="dof.focus_distance", frame=frame) print("Camera rig setup complete.") # Example usage -setup_camera_rig("BezierCurve", "Cube", num_frames=120, fps=24, initial_focal_length=35) +setup_camera_rig("BezierCurve", "Cube", initial_focal_length=35) + From d860d6d167dc0935b91327782a8e77d0d53f6e11 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 9 Mar 2024 18:06:00 +0100 Subject: [PATCH 3/4] Create camrig.py --- camrig.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 camrig.py diff --git a/camrig.py b/camrig.py new file mode 100644 index 0000000..9e27c41 --- /dev/null +++ b/camrig.py @@ -0,0 +1,73 @@ +import bpy +from mathutils import Vector + +def update_camera_focal_length(camera, target_object, scale_factor=0.7): + """ + Updates the camera's focal length based on the target object's size and distance, + aiming to keep the target object's largest dimension within a specified percentage + of the frame's width or height. + + Args: + - camera: The camera object to update. + - target_object: The target object the camera focuses on. + - scale_factor (float): Determines how much of the frame's width or height + the target object should occupy. Defaults to 0.7 (70%). + """ + camera_loc = camera.matrix_world.translation + target_loc = target_object.matrix_world.translation + distance = (target_loc - camera_loc).length + + # Calculate dimensions of the target object + dimensions = target_object.dimensions + max_dimension = max(dimensions.x, dimensions.y, dimensions.z) + + # Assuming a sensor width of 36mm (default in Blender) and frame aspect ratio + sensor_width = camera.data.sensor_width + aspect_ratio = bpy.context.scene.render.resolution_x / bpy.context.scene.render.resolution_y + frame_dimension = sensor_width if aspect_ratio >= 1 else sensor_width / aspect_ratio + + # Calculate focal length to fit the object within the specified scale factor of the frame + focal_length = (distance * camera.data.lens) / (max_dimension / frame_dimension * scale_factor) + camera.data.lens = focal_length + +def setup_camera_rig(curve_name, target_object_name, initial_focal_length=50): + """ + Sets up a camera rig that moves along a Bézier curve focusing on a specified object, + adjusting the camera's focal length dynamically. + + Args: + - curve_name (str): The name of the Bézier curve object. + - target_object_name (str): The name of the object to focus on. + - initial_focal_length (float): The initial focal length of the camera. + """ + curve = bpy.data.objects.get(curve_name) + target_object = bpy.data.objects.get(target_object_name) + if not curve or not target_object: + print("Curve or target object does not exist.") + return + + camera = bpy.data.objects.get("CameraRig") or bpy.ops.object.camera_add().name + camera.data.sensor_fit = 'HORIZONTAL' + camera.data.lens = initial_focal_length + + follow_path_constraint = camera.constraints.new(type='FOLLOW_PATH') + follow_path_constraint.target = curve + follow_path_constraint.use_curve_follow = True + + track_to_constraint = camera.constraints.new(type='TRACK_TO') + track_to_constraint.target = target_object + track_to_constraint.track_axis = 'TRACK_NEGATIVE_Z' + track_to_constraint.up_axis = 'UP_Y' + + start_frame = bpy.context.scene.frame_start + end_frame = bpy.context.scene.frame_end + + for frame in range(start_frame, end_frame + 1): + bpy.context.scene.frame_set(frame) + update_camera_focal_length(camera, target_object) + camera.data.keyframe_insert(data_path="lens", frame=frame) + + print("Camera rig setup complete.") + +# Example usage +setup_camera_rig("BezierCurve", "Cube", initial_focal_length=35) From 4c366039455a33db1b6f7a0e86717625ad659c03 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 9 Mar 2024 18:16:54 +0100 Subject: [PATCH 4/4] Create utility.py --- utility.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 utility.py diff --git a/utility.py b/utility.py new file mode 100644 index 0000000..3006f22 --- /dev/null +++ b/utility.py @@ -0,0 +1,32 @@ +import bpy + +def find_curve_and_target(): + """ + Identifies the selected curve and target object from the Blender scene. + + Returns: + - curve_name (str): The name of the selected curve. + - target_object_name (str): The name of the selected target object. + + If the correct selection is not made, returns None for both. + """ + selected_objects = bpy.context.selected_objects + if len(selected_objects) != 2: + print("Please select exactly two objects: one curve and one target object.") + return None, None + + # Determine which object is the curve and which is the target + curve = next((obj for obj in selected_objects if obj.type == 'CURVE'), None) + target_object = next((obj for obj in selected_objects if obj.type != 'CURVE'), None) + + if not curve or not target_object: + print("Selection does not include a curve and another object.") + return None, None + + return curve.name, target_object.name + +# Example usage +curve_name, target_object_name = find_curve_and_target() +if curve_name and target_object_name: + print(f"Curve: {curve_name}, Target Object: {target_object_name}") + # You can now use curve_name and target_object_name with the setup_camera_rig function