Created
March 3, 2025 16:05
-
-
Save LiamHz/3945b79c53742f7e02eb50329c915563 to your computer and use it in GitHub Desktop.
overlay_tools/viewport_camera_hud/camera_notes_in_view.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Shot notes display in camera view | |
""" | |
import bpy | |
import blf | |
import gpu | |
from gpu_extras.batch import batch_for_shader | |
from mathutils import Vector | |
from storyliner.config import config | |
from storyliner.config import wksl_logging | |
from .camera_hud_bgl import view3d_camera_border | |
_logger = wksl_logging.getLogger(__name__) | |
def draw_test_text(context, x, y, text): | |
"""Test function to draw text with different methods for debugging""" | |
font_id = 0 | |
# Method 1: Standard BLF draw | |
blf.color(font_id, 1.0, 0.0, 0.0, 1.0) # Red | |
blf.position(font_id, x, y, 0) | |
if config.isBlenderVersionSupOrEqualTo((4, 0, 0)): | |
blf.size(font_id, 24) | |
else: | |
blf.size(font_id, 24, 72) | |
blf.draw(font_id, f"M1: {text}") | |
# Method 2: Alternative for Blender 4.3+ | |
try: | |
y += 30 | |
# Some newer versions might require different method calls | |
blf.color(font_id, 0.0, 1.0, 0.0, 1.0) # Green | |
blf.position(font_id, x, y, 0) | |
if hasattr(blf, 'draw_simple'): | |
blf.draw_simple(font_id, f"M2: {text}") | |
else: | |
blf.draw(font_id, f"M2: {text}") | |
except Exception as e: | |
print(f"Method 2 failed: {e}") | |
# Method 3: Using different draw parameters | |
try: | |
y += 30 | |
blf.color(font_id, 0.0, 0.0, 1.0, 1.0) # Blue | |
blf.position(font_id, x, y, 0) | |
# Try with explicit parameters | |
blf.draw(font_id, f"M3: {text}") | |
except Exception as e: | |
print(f"Method 3 failed: {e}") | |
class WkStoryLiner_OT_DrawShotNotesInCameraView(bpy.types.Operator): | |
bl_idname = "wkstoryliner.draw_shot_notes_in_camera_view" | |
bl_label = "StoryLiner Draw Shot Notes in Camera View" | |
bl_description = "Display shot notes inside the camera view when in camera perspective" | |
bl_options = {"REGISTER", "INTERNAL"} | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
self.draw_handle = None | |
def invoke(self, context, event): | |
self.register_handlers(context) | |
context.window_manager.modal_handler_add(self) | |
return {"RUNNING_MODAL"} | |
def register_handlers(self, context): | |
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.draw, (context,), "WINDOW", "POST_PIXEL") | |
def unregister_handlers(self, context): | |
if self.draw_handle is not None: | |
bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW") | |
self.draw_handle = None | |
def modal(self, context, event): | |
# Always pass through, we just need to keep the handler alive | |
props = config.getAddonProps(context.scene) | |
if not props.shot_notes_display_in_camera_view: | |
self.unregister_handlers(context) | |
return {"CANCELLED"} | |
return {"PASS_THROUGH"} | |
def cancel(self, context): | |
self.unregister_handlers(context) | |
def draw(self, context): | |
if not hasattr(context.scene, "WkStoryLiner_props"): | |
_logger.debug_ext("Error in WkStoryLiner_DrawShotNotesInCameraView draw: no WkStoryLiner_props defined") | |
return | |
props = config.getAddonProps(context.scene) | |
# Return if the feature is disabled | |
if not props.shot_notes_display_in_camera_view: | |
return | |
# Return if we're not in camera view | |
if context.space_data.region_3d.view_perspective != "CAMERA": | |
return | |
# Return if there's no active camera | |
cam = context.scene.camera | |
if cam is None or "CAMERA" != cam.type or cam.name not in context.scene.objects: | |
return | |
# Get the current shot | |
current_shot = props.getCurrentShot() | |
if current_shot is None: | |
return | |
# Only continue if the shot has notes | |
if not current_shot.hasNotes(): | |
return | |
# Only display shot notes if the overlay is enabled | |
if not bpy.context.space_data.overlay.show_overlays: | |
return | |
self.draw_shot_notes(context, current_shot) | |
def draw_shot_notes(self, context, shot): | |
print("draw_shot_notes()") | |
try: | |
# Get camera borders to position the text | |
u_r_corner, d_r_corner, d_l_corner, u_l_corner = view3d_camera_border(context) | |
# Calculate text positioning | |
region_width = context.region.width | |
region_height = context.region.height | |
# Debug print for camera borders | |
print(f"Camera borders: UR: {u_r_corner}, DR: {d_r_corner}, DL: {d_l_corner}, UL: {u_l_corner}") | |
print(f"Region dimensions: {region_width}x{region_height}") | |
# Use a semi-transparent black background for better readability | |
self.draw_background(context, shot) | |
# Draw test text at known positions for debugging | |
mid_x = region_width / 2 | |
mid_y = region_height / 2 | |
draw_test_text(context, mid_x - 100, mid_y, "TEST TEXT CENTER") | |
draw_test_text(context, 100, 100, "TEST TEXT BOTTOM LEFT") | |
# Get notes from the shot | |
notes = [] | |
if shot.note01: | |
notes.append(shot.note01) | |
if shot.note02: | |
notes.append(shot.note02) | |
if shot.note03: | |
notes.append(shot.note03) | |
if not notes: | |
return | |
# Calculate text position - centered at the bottom part of the camera view | |
font_id = 0 # Default font | |
font_size = 24 # Increased from 16 to 24 for better visibility | |
try: | |
# For Blender version 4 and higher: | |
if config.isBlenderVersionSupOrEqualTo((4, 0, 0)): | |
blf.size(font_id, font_size) | |
else: | |
blf.size(font_id, font_size, 72) | |
except Exception as e: | |
print(f"Error setting font size: {e}") | |
try: | |
# Enable font shadow for better readability | |
blf.enable(font_id, blf.SHADOW) | |
blf.shadow(font_id, 5, 0.0, 0.0, 0.0, 1.0) # Increased shadow size and opacity | |
blf.shadow_offset(font_id, 2, -2) # Increased shadow offset | |
except Exception as e: | |
print(f"Error setting font shadow: {e}") | |
# Draw each note line | |
y_offset = d_l_corner.y + 60 # Increased padding from bottom for better visibility | |
line_height = font_size * 1.5 | |
for i, note in enumerate(notes): | |
if not note.strip(): | |
continue | |
try: | |
# Get text dimensions | |
text_width, text_height = blf.dimensions(font_id, note) | |
# Debug print for text positioning | |
print(f"Note {i+1}: '{note}', width: {text_width}, height: {text_height}") | |
# Center the text horizontally | |
x_pos = region_width / 2 - text_width / 2 | |
# Debug print for final position | |
print(f"Drawing at position: ({x_pos}, {y_offset})") | |
# Draw the text | |
blf.color(font_id, 1.0, 1.0, 0.0, 1.0) # Changed to yellow text for better visibility | |
blf.position(font_id, x_pos, y_offset, 0) | |
# Try both ways of drawing text | |
try: | |
blf.draw(font_id, note) | |
except Exception as e1: | |
print(f"Error with blf.draw: {e1}") | |
try: | |
# Try alternate method if available | |
if hasattr(blf, 'draw_simple'): | |
blf.draw_simple(font_id, note) | |
except Exception as e2: | |
print(f"Error with alternative draw method: {e2}") | |
# Move up for the next line | |
y_offset += line_height | |
except Exception as e: | |
print(f"Error drawing note {i+1}: {e}") | |
try: | |
# Disable shadow after drawing | |
blf.disable(font_id, blf.SHADOW) | |
except Exception as e: | |
print(f"Error disabling font shadow: {e}") | |
except Exception as e: | |
print(f"Error in draw_shot_notes: {e}") | |
import traceback | |
traceback.print_exc() | |
def draw_background(self, context, shot): | |
# Draw a semi-transparent background behind the notes for better readability | |
notes_count = sum(1 for note in [shot.note01, shot.note02, shot.note03] if note.strip()) | |
if notes_count == 0: | |
return | |
# Get camera borders | |
u_r_corner, d_r_corner, d_l_corner, u_l_corner = view3d_camera_border(context) | |
# Calculate background dimensions | |
padding = 20 | |
line_height = 24 | |
bg_height = notes_count * line_height + padding * 2 | |
# Background positioned at the bottom of the camera view | |
x_min = d_l_corner.x | |
x_max = d_r_corner.x | |
y_min = d_l_corner.y + 20 # Some padding from the bottom | |
y_max = y_min + bg_height | |
# Draw background rectangle | |
gpu.state.blend_set('ALPHA') | |
shader = gpu.shader.from_builtin('UNIFORM_COLOR') | |
shader.bind() | |
shader.uniform_float("color", (0.0, 0.0, 0.0, 0.5)) # Semi-transparent black | |
vertices = ((x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)) | |
indices = ((0, 1, 2), (0, 2, 3)) | |
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices) | |
batch.draw(shader) | |
gpu.state.blend_set('NONE') | |
def register(): | |
bpy.utils.register_class(WkStoryLiner_OT_DrawShotNotesInCameraView) | |
def unregister(): | |
bpy.utils.unregister_class(WkStoryLiner_OT_DrawShotNotesInCameraView) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment