-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathalign_to_camera.py
More file actions
103 lines (78 loc) · 3.06 KB
/
align_to_camera.py
File metadata and controls
103 lines (78 loc) · 3.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""
Align to Camera - Rotates selected objects to face the active camera.
Classic "billboard" operation: each selected object is rotated so the
chosen local axis points toward the camera. Ideal for sprites, decals,
signs, and any flat element that should always face the viewer.
"""
import bpy
import mathutils
from typing import Optional
# Safe up-axis for each track axis (must be on a different world axis)
_TRACK_UP = {
'X': 'Z', '-X': 'Z',
'Y': 'Z', '-Y': 'Z',
'Z': 'Y', '-Z': 'Y',
}
def align_to_camera(context, track_axis: str) -> Optional[str]:
"""Rotate each selected object so its track_axis faces the active camera.
Args:
context: Current Blender context.
track_axis: Object-space axis to align toward the camera.
Must be one of: 'X', '-X', 'Y', '-Y', 'Z', '-Z'.
Returns:
Error string on failure, None on success.
"""
camera = context.scene.camera
if not camera:
return "No active camera in the scene — set one in Scene Properties"
cam_loc = camera.matrix_world.translation
up_axis = _TRACK_UP.get(track_axis, 'Z')
aligned = 0
for obj in context.selected_objects:
if obj == camera:
continue
direction = cam_loc - obj.matrix_world.translation
if direction.length < 1e-6:
continue # Object is at the camera — skip
direction.normalize()
rot_quat = direction.to_track_quat(track_axis, up_axis)
obj.rotation_euler = rot_quat.to_euler()
aligned += 1
if aligned == 0:
return "No objects could be aligned — are they all at the camera position?"
return None
class OBJECT_OT_align_to_camera(bpy.types.Operator):
"""Rotate selected objects so the chosen axis faces the active camera"""
bl_idname = "object.align_to_camera"
bl_label = "Align to Camera"
bl_description = (
"Rotate each selected object so its chosen local axis points toward "
"the active camera. Classic billboard effect for sprites and decals. "
"Requires an active camera to be set in the scene"
)
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (
context.mode == 'OBJECT' and
len(context.selected_objects) > 0 and
context.scene.camera is not None
)
def execute(self, context):
wm = context.window_manager
props = wm.pivotier.align_to_camera
error = align_to_camera(context, props.track_axis)
if error:
self.report({'WARNING'}, error)
return {'CANCELLED'}
return {'FINISHED'}
# ─── Registration ─────────────────────────────────────────────────────────────
classes = (
OBJECT_OT_align_to_camera,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)