-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathset_pivot_to_face.py
More file actions
144 lines (112 loc) · 4.59 KB
/
set_pivot_to_face.py
File metadata and controls
144 lines (112 loc) · 4.59 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
Set Pivot to Face - Sets the origin to any of the 6 bounding box face centers.
Extends the concept of Set Pivot to Base (-Z face) to all six faces of the
bounding box. Useful for precise mirroring (pivot on the mirror plane face),
surface-snapping, and placing objects flush against walls or floors.
"""
import bpy
import mathutils
from typing import Optional
def get_face_center_world(obj: bpy.types.Object, face: str) -> mathutils.Vector:
"""Return the world-space center of the given bounding box face.
Args:
obj: Target Blender object.
face: Face identifier — one of:
'NEG_X', 'POS_X', 'NEG_Y', 'POS_Y', 'NEG_Z', 'POS_Z'
Returns:
World-space position of that face's center.
"""
local_bbox = [mathutils.Vector(c) for c in obj.bound_box]
prefix, axis_name = face.split('_')
axis_idx = ('X', 'Y', 'Z').index(axis_name)
use_max = (prefix == 'POS')
# Extreme value on the face axis
vals_on_axis = [v[axis_idx] for v in local_bbox]
extreme = max(vals_on_axis) if use_max else min(vals_on_axis)
# Midpoint on the remaining two axes
face_center = mathutils.Vector()
for i in range(3):
if i == axis_idx:
face_center[i] = extreme
else:
other_vals = [v[i] for v in local_bbox]
face_center[i] = (min(other_vals) + max(other_vals)) / 2
return obj.matrix_world @ face_center
def set_pivot_to_face(context, face: str) -> Optional[str]:
"""Set origin to the chosen bounding box face center for each selected mesh.
Args:
context: Current Blender context.
face: Face identifier ('NEG_X', 'POS_Z', etc.)
Returns:
Error string on failure, None on success.
"""
objects = [o for o in context.selected_objects if o.type == 'MESH']
if not objects:
return "No mesh objects selected"
cursor = context.scene.cursor
saved_loc = cursor.location.copy()
saved_rot = cursor.rotation_euler.copy()
active_obj = context.view_layer.objects.active
sel_states = {o: o.select_get() for o in bpy.data.objects}
try:
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
obj.select_set(True)
context.view_layer.objects.active = obj
cursor.location = get_face_center_world(obj, face)
cursor.rotation_euler = obj.rotation_euler.copy()
bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
obj.select_set(False)
finally:
cursor.location = saved_loc
cursor.rotation_euler = saved_rot
for o, was_sel in sel_states.items():
o.select_set(was_sel)
if active_obj:
context.view_layer.objects.active = active_obj
return None
class OBJECT_OT_set_pivot_to_face(bpy.types.Operator):
"""Set the origin to the center of a bounding box face"""
bl_idname = "object.set_pivot_to_face"
bl_label = "Set Pivot to Face"
bl_description = (
"Move the origin to the center of the chosen bounding box face. "
"Useful for non-destructive mirroring and surface-to-surface placement"
)
bl_options = {'REGISTER', 'UNDO'}
face: bpy.props.EnumProperty(
name="Face",
description="Which bounding box face center to use as the new pivot",
items=[
('NEG_Z', "-Z Bottom", "Bottom face — equivalent to Set Pivot to Base"),
('POS_Z', "+Z Top", "Top face"),
('NEG_X', "-X Left", "Left face (negative X)"),
('POS_X', "+X Right", "Right face (positive X)"),
('NEG_Y', "-Y Front", "Front face (negative Y)"),
('POS_Y', "+Y Back", "Back face (positive Y)"),
],
default='NEG_Z',
options={'SKIP_SAVE'},
)
@classmethod
def poll(cls, context):
return (
context.mode == 'OBJECT' and
any(o.type == 'MESH' for o in context.selected_objects)
)
def execute(self, context):
error = set_pivot_to_face(context, self.face)
if error:
self.report({'WARNING'}, error)
return {'CANCELLED'}
return {'FINISHED'}
# ─── Registration ─────────────────────────────────────────────────────────────
classes = (
OBJECT_OT_set_pivot_to_face,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)