-
Notifications
You must be signed in to change notification settings - Fork 0
/
mr_tempPin_pivotFromSelectionSet.py
310 lines (263 loc) · 11.9 KB
/
mr_tempPin_pivotFromSelectionSet.py
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
"""
# ------------------------------------------------------------------------------ #
# SCRIPT: mr_tempPin_pivotFromSelectionSet.py
# VERSION: 0009
#
# CREATORS: Maria Robertson
# ---------------------------------------
#
# ---------------------------------------
# DESCRIPTION:
# ---------------------------------------
# A script to create an temp offset group to pivot with, from a predetermined object.
# Originally created when wanting to quickly rotate IK controls in an FK manner. (e.g. rotating an IK foot around a pelvis)
#
# INSTRUCTIONS:
# ---------------------------------------
# PHASE 1:
# Tag an object to pivot from with this command:
# mr_tempPin_pivotFromSelectionSet.create_follow_selection_set()
#
# PHASE 2:
# Select the objects you'd like to pivot, and then run the pivot_from_follow_selection_set(mode, time) function, with the desired settings:
# mode:
# - "both" - constrain translate and rotate attributes
# - "translate" - constrain just translate attributes, leaving rotations unaffected.
#
# time:
# - "frame" - bake the temp pivot only on the curernt frame. For small on-frame adjustments.
# - "range" - bake the temp pivot for the whole playback range.
#
# PHASE 3:
# When ready, run the same function used in PHASE 2, to set keys on the current frame for all of the temp pivot's targets, then delete temp pivot.
#
# EXAMPLE USES:
# ---------------------------------------
# I personally use two hotkeys for this script.
# - CTRL + SHIFT + ALT + D to tag an object to pivot from.
# - CTRL + ALT + D to cycle between creating and deleting the temp pivot.
#
# If I want to often pivot around specific points of a mesh, I sometimes create rivet locators on it, so I can quickly tag them as pivots.
#
# ---------------------------------------
# RUN COMMANDS:
# ---------------------------------------
import importlib
import mr_tempPin_pivotFromSelectionSet
importlib.reload(mr_tempPin_pivotFromSelectionSet)
# To tag an object to pivot from.
mr_tempPin_pivotFromSelectionSet.create_follow_selection_set()
# Use one of the following commads to pivot with:
mr_tempPin_pivotFromSelectionSet.pivot_from_follow_selection_set("both", "frame")
mr_tempPin_pivotFromSelectionSet.pivot_from_follow_selection_set("both", "range")
mr_tempPin_pivotFromSelectionSet.pivot_from_follow_selection_set("translate", "frame")
mr_tempPin_pivotFromSelectionSet.pivot_from_follow_selection_set("translate", "range")
# ---------------------------------------
# REQUIREMENTS:
# ---------------------------------------
# This script requires functions from the following files:
# - mr_find_constraint_targets_and_drivers.py
# - mr_utilities.py
#
# ---------------------------------------
# CHANGELOG:
# ---------------------------------------
# 2024-01-14 - 0009:
# - Added are_translations_or_rotations_constrained().
# - Before, script would error if object had unconstrained translates and rotates, but constrained scales.
#
# 2023-12-18 - 0008:
# - Add warning if follow_set_name does not exist.
#
# 2023-12-18 - 0007:
# - Added option to create temp pivot on either the current frame or whole playback range.
# - Adding to script description.
#
# 2023-11-28 - 0006:
# - Fixing typos with quotation marks.
#
# 2023-11-28 - 0005:
# - Splitting script to two options:
# 1: pivot with translation and rotation
# 2: pivot with just translation
#
# 2023-11-20 - 0004:
# - Converted original MEL script to Python.
#
# 2023-06-05 - 0003:
# - Fixing bug that would think objects had constraints when they didn't.
#
# 2023-04-11 - 0002:
# - Added two possible options to pivot with.
# ------------------------------------------------------------------------------ //
"""
import maya.cmds as cmds
import maya.mel as mel
import importlib
import mr_find_constraint_targets_and_drivers
importlib.reload(mr_find_constraint_targets_and_drivers)
import importlib
import mr_utilities
importlib.reload(mr_utilities)
########################################################################
# #
# CREATE A SELECTION SET TO FOLLOW #
# #
########################################################################
# ------------------------------------------------------------------------------ #
def create_follow_selection_set():
follow_set_name = "objToFollowSet"
# If nothing or more than one object is selected,
if len(cmds.ls(selection=True)) != 1:
# NOTE: make sure -title is unique, otherwise dialog won't trigger
cmds.confirmDialog(title="Error A", message="Select one object to tag as a pivot.")
else:
# If the set already exists, delete it.
if cmds.objExists(follow_set_name):
cmds.delete(follow_set_name)
# Create a selection set for the selected object.
obj_to_follow_set = cmds.sets(name=follow_set_name)
########################################################################
# #
# CREATE AN OFFSET GROUP TO PIVOT WITH SET TO FOLLOW #
# #
########################################################################
# ------------------------------------------------------------------------------ #
def pivot_from_follow_selection_set(mode=None, time=None):
if mode != "both" and mode != "translate":
cmds.warning("Please input a valid manipulation mode.")
return
if time != "range" and time != "frame":
cmds.warning("Please choose the length of frames the pivot should be baked on.")
return
# -------------------------------------------------------------------
# 01. INITIALIZE VARIABLES
# -------------------------------------------------------------------
temp_pivot_offset_group_name = "TEMP_pivot_offset"
follow_set_name = "objToFollowSet"
objs_to_pivot = cmds.ls(selection=True)
if not cmds.objExists(follow_set_name):
cmds.warning(f"The selection set \"{follow_set_name}\" hasn't been created yet.")
return
cmds.select(follow_set_name)
follow_object = cmds.ls(selection=True)
# If the selected object has any constraints, cancel script.
for obj in objs_to_pivot:
is_trans_rot_constrained = are_translations_or_rotations_constrained(obj)
if is_trans_rot_constrained:
cmds.confirmDialog(title="Error D", message="Selected object has constraints.")
cmds.select(objs_to_pivot)
raise RuntimeError("Selected objects have constraints, please remove them first.")
return
# -------------------------------------------------------------------
# 01. IF AN OFFSET GROUP ALREADY EXISTS, KEY THE TARGETS AND DELETE THE GROUP
# -------------------------------------------------------------------
if cmds.objExists(temp_pivot_offset_group_name):
# delete its connections.
cmds.delete(temp_pivot_offset_group_name, constraints=True)
temp_pivot_offset_group_children = cmds.listRelatives(temp_pivot_offset_group_name, children=True) or []
# Set keys on children.
cmds.select(temp_pivot_offset_group_children)
mr_find_constraint_targets_and_drivers.mr_find_targets_of_selected()
mel.eval("SetKeyTranslate ;")
mel.eval("SetKeyRotate ;")
# Delete the offset group.
cmds.delete(temp_pivot_offset_group_name)
# End with the Translate manipulator on.
mel.eval("buildTranslateMM ;")
mel.eval("destroySTRSMarkingMenu MoveTool ;")
# -------------------------------------------------------------------
# 01. IF AN OFFSET GROUP DOESN'T EXIST, CREATE ONE AND SET-UP A TEMP PIVOT.
# -------------------------------------------------------------------
# If the selection set exists,
elif cmds.objExists(follow_set_name):
# -------------------------------------------------------------------
# 02. CHECK SELECTION
# -------------------------------------------------------------------
# If nothing or more than one object is selected,
if len(objs_to_pivot) == 0:
# NOTE: make sure -title is unique, otherwise dialog won't trigger
cmds.confirmDialog(title="Error A", message="Select objects to pivot.")
return
# If the selection set object is selected as the pivot,
elif objs_to_pivot[0] == follow_object[0]:
cmds.confirmDialog(title="Error B", message="Can't use an object marked to pivot from as the pivot.")
return
# -------------------------------------------------------------------
# 02. CREATE AN OFFSET GROUP TO PIVOT
# -------------------------------------------------------------------
temp_pivot_offset_group = cmds.group(em=True, name=temp_pivot_offset_group_name)
# Match its translate and rotate to the follow object.
point_constraint = cmds.pointConstraint(follow_object[0], temp_pivot_offset_group, weight=1)
orient_constraint = cmds.orientConstraint(follow_object[0], temp_pivot_offset_group, weight=1)
# NOTE: Have to specifically name the constraint to delete, otherwise the null
# group itself gets deleted too if "delete -cn" or "DeleteConstraints" is used
if time == "frame":
cmds.setKeyframe(temp_pivot_offset_group, attribute="translate")
elif time == "range":
start_time = cmds.playbackOptions(query=True, min=True)
end_time = cmds.playbackOptions(query=True, max=True)
attributes = ["translateX", "translateY", "translateZ","rotateX", "rotateY", "rotateZ"]
cmds.refresh(suspend=True)
cmds.bakeResults(
temp_pivot_offset_group,
attribute=attributes,
simulation=False,
time=(start_time, end_time)
)
cmds.delete(temp_pivot_offset_group, staticChannels=True)
cmds.refresh(suspend=False)
cmds.delete(point_constraint)
cmds.delete(orient_constraint)
for obj in objs_to_pivot:
loc = cmds.spaceLocator(name=obj + "_temp_pivot_loc")[0]
cmds.parent(loc, temp_pivot_offset_group)
# Parent constrain loc to objs_to_pivot.
cmds.parentConstraint(obj, loc, weight=1)
cmds.delete(loc, constraints=True)
"""
if mode == "both":
cmds.pointConstraint(loc, obj, maintainOffset=True, weight=1)
cmds.orientConstraint(loc, obj, maintainOffset=True, weight=1)
elif mode == "translate":
cmds.pointConstraint(loc, obj, maintainOffset=True, weight=1)
"""
if mode == "both":
# Point and orient constrain.
mr_utilities.constrain_unlocked_attributes(loc, obj, mode="both")
elif mode == "translate":
# Point constrain.
mr_utilities.constrain_unlocked_attributes(loc, obj, "translate")
cmds.select(temp_pivot_offset_group, replace=True)
# End with rotate manipulator active.
mel.eval("buildRotateMM ;")
mel.eval("destroySTRSMarkingMenu RotateTool ;")
# -------------------------------------------------------------------
# 01. IF NO SELECTION SET EXISTS, GIVE A WARNING
# -------------------------------------------------------------------
else:
# (make sure -title is unique; otherwise, dialog won't trigger)
cmds.confirmDialog(title="Error C", message="Create a follow selection set first.")
##################################################################################################################################################
########################################################################
# #
# SUPPORTING FUNCTIONS #
# #
########################################################################
# ------------------------------------------------------------------------------ #
def are_translations_or_rotations_constrained(object):
attributes = [
f"{object}.translateX",
f"{object}.translateY",
f"{object}.translateZ",
f"{object}.rotateX",
f"{object}.rotateY",
f"{object}.rotateZ"
]
are_attributes_constrained = False
for attr in attributes:
has_constraint, constraint_nodes, constraint_relatives = mr_utilities.is_constrained(attr)
if has_constraint:
are_attributes_constrained = True
break
return are_attributes_constrained