Skip to content

Instantly share code, notes, and snippets.

@LiamHz
Created March 3, 2025 16:05
Show Gist options
  • Save LiamHz/3945b79c53742f7e02eb50329c915563 to your computer and use it in GitHub Desktop.
Save LiamHz/3945b79c53742f7e02eb50329c915563 to your computer and use it in GitHub Desktop.
overlay_tools/viewport_camera_hud/camera_notes_in_view.py
"""
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