Skip to content

Commit 0195320

Browse files
committed
Shifter: Improving custom steps implementation and new method to run Sub-custom_Steps Closes #601
1 parent a302005 commit 0195320

1 file changed

Lines changed: 261 additions & 15 deletions

File tree

release/scripts/mgear/shifter/custom_step.py

Lines changed: 261 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import mgear.pymaya as pm
22
from mgear.core import attribute
33
from maya import cmds
4+
import sys
5+
import importlib
6+
import os
47

58

69
class customShifterMainStep(object):
7-
'''
10+
"""
811
Main Class for shifter custom steps
9-
'''
12+
"""
1013

1114
def __init__(self, stored_dict):
12-
"""Constructor
13-
"""
15+
"""Constructor"""
1416
self._step_dict = stored_dict
1517
# init local setup root var
1618
# this should be a group to organize the objects of a local setup
@@ -19,14 +21,37 @@ def __init__(self, stored_dict):
1921

2022
@property
2123
def mgear_run(self):
22-
"""Returns the resulting object of the 'mgearRun' step.
23-
"""
24+
"""Returns the resulting object of the 'mgearRun' step."""
2425
if "mgearRun" not in self._step_dict:
2526
raise Exception(
2627
"Can't access the 'mgearRun' in pre steps \
27-
or when running individual steps.")
28+
or when running individual steps."
29+
)
30+
31+
return self._step_dict.get("mgearRun")
32+
33+
@property
34+
def rig(self):
35+
"""Alias for mgear_run for more intuitive access.
36+
37+
Returns:
38+
mgearRun: The shifter rig object
39+
40+
Example:
41+
global_ctl = self.rig.global_ctl
42+
"""
43+
return self.mgear_run
44+
45+
@property
46+
def guide(self):
47+
"""Access to the guide if available.
2848
29-
return self._step_dict.get('mgearRun')
49+
Returns:
50+
Guide: The guide object from the rig build
51+
"""
52+
if hasattr(self.mgear_run, "guide"):
53+
return self.mgear_run.guide
54+
return None
3055

3156
def component(self, name):
3257
"""Access to components from the current build process.
@@ -41,6 +66,41 @@ def component(self, name):
4166
raise KeyError("Could not find the '{}' component.".format(name))
4267
return self.mgear_run.components[name]
4368

69+
def components_by_type(self, comp_type):
70+
"""Get all components of a specific type.
71+
72+
Args:
73+
comp_type (str): The component type (e.g., "control_01", "arm_2jnt_01")
74+
75+
Returns:
76+
list: List of matching components
77+
78+
Example:
79+
all_controls = self.components_by_type("control_01")
80+
for ctrl in all_controls:
81+
print(ctrl.fullName)
82+
"""
83+
matching = []
84+
for comp_name, comp in self.mgear_run.components.items():
85+
if comp.type == comp_type:
86+
matching.append(comp)
87+
return matching
88+
89+
def has_component(self, name):
90+
"""Check if a component exists without raising an error.
91+
92+
Args:
93+
name (str): The name of the component
94+
95+
Returns:
96+
bool: True if component exists, False otherwise
97+
98+
Example:
99+
if self.has_component("arm_L0"):
100+
arm = self.component("arm_L0")
101+
"""
102+
return name in self.mgear_run.components
103+
44104
def custom_step(self, name):
45105
"""Allows access to custom steps that have already ran in the past.
46106
@@ -53,25 +113,211 @@ def custom_step(self, name):
53113
if name not in self._step_dict:
54114
raise KeyError(
55115
"The custom step '{}' does not exist, or \
56-
did not run yet.".format(name))
116+
did not run yet.".format(
117+
name
118+
)
119+
)
57120

58121
return self._step_dict[name]
59122

123+
def has_custom_step(self, name):
124+
"""Check if a custom step exists without raising an error.
125+
126+
Args:
127+
name (str): The name of the custom step
128+
129+
Returns:
130+
bool: True if custom step exists, False otherwise
131+
132+
Example:
133+
if self.has_custom_step("proxy_setup"):
134+
proxy = self.custom_step("proxy_setup")
135+
"""
136+
return name in self._step_dict
137+
138+
def run_sub_step(self, module_name, step_path=None):
139+
"""Load and run a sub custom step from an external module.
140+
141+
This method allows you to dynamically load and execute another custom step
142+
from a separate Python file. The sub-step will have access to the same
143+
step dictionary, components, and mgear_run context as the parent step.
144+
145+
The method will automatically:
146+
- Check for MGEAR_SHIFTER_CUSTOMSTEP_PATH environment variable if no step_path is provided
147+
- Add the step_path to sys.path if provided and not already present
148+
- Import and reload the module to get the latest changes
149+
- Find the class that inherits from customShifterMainStep
150+
- Instantiate it with the same step dictionary
151+
- Run its setup() and run() methods
152+
- Register it in the step dictionary for later access
153+
154+
Args:
155+
module_name (str): The name of the Python module/file without the .py extension.
156+
Example: "my_other_step" for file "my_other_step.py"
157+
step_path (str, optional): The absolute directory path where the module is located.
158+
If None, will check MGEAR_SHIFTER_CUSTOMSTEP_PATH environment variable.
159+
If neither is provided, assumes the module is already in Python's path.
160+
Example: "w:/my_steps" or "C:/pipeline/custom_steps"
161+
162+
Returns:
163+
customShifterMainStep: The instantiated and executed sub-step object.
164+
You can use this to access attributes from the sub-step
165+
immediately after execution.
166+
167+
Raises:
168+
ImportError: If the module cannot be found or imported
169+
AttributeError: If the module does not contain a class that inherits
170+
from customShifterMainStep
171+
172+
Examples:
173+
# Run a sub-step from a specific path
174+
self.run_sub_step("apply_colors", "w:/my_steps")
175+
176+
# Run a sub-step using MGEAR_SHIFTER_CUSTOMSTEP_PATH env var
177+
self.run_sub_step("apply_colors")
178+
179+
# Run a sub-step that's already in the Python path
180+
self.run_sub_step("mirror_controls")
181+
182+
# Access the sub-step immediately after running
183+
color_step = self.run_sub_step("apply_colors", "w:/my_steps")
184+
print(color_step.colors_applied)
185+
186+
# Access the sub-step later using custom_step()
187+
self.run_sub_step("apply_colors", "w:/my_steps")
188+
# ... other code ...
189+
colors = self.custom_step("apply_colors").colors_applied
190+
191+
Note:
192+
- The module must contain exactly one class that inherits from
193+
customShifterMainStep (excluding the base class itself)
194+
- The sub-step's name (set in its setup() method) determines how
195+
it can be accessed later via self.custom_step("name")
196+
- The module is reloaded each time to ensure you get the latest version
197+
during development
198+
- If step_path is not provided, the method will check for the
199+
MGEAR_SHIFTER_CUSTOMSTEP_PATH environment variable
200+
"""
201+
self.log("Running sub-step: '{}'".format(module_name))
202+
203+
# If no step_path provided, check for environment variable
204+
if step_path is None:
205+
step_path = os.environ.get("MGEAR_SHIFTER_CUSTOMSTEP_PATH")
206+
207+
# Add path if provided and not already in sys.path
208+
if step_path and step_path not in sys.path:
209+
sys.path.insert(0, step_path)
210+
211+
# Import the module
212+
try:
213+
module = importlib.import_module(module_name)
214+
# Reload to ensure we get the latest version
215+
importlib.reload(module)
216+
except ImportError as e:
217+
raise ImportError(
218+
"Could not import module '{}': {}".format(module_name, e)
219+
)
220+
221+
# Find the CustomShifterStep class in the module
222+
step_class = None
223+
for item_name in dir(module):
224+
item = getattr(module, item_name)
225+
# Check if it's a class and a subclass of customShifterMainStep
226+
# but not customShifterMainStep itself
227+
if (
228+
isinstance(item, type)
229+
and issubclass(item, customShifterMainStep)
230+
and item is not customShifterMainStep
231+
):
232+
step_class = item
233+
break
234+
235+
if not step_class:
236+
raise AttributeError(
237+
"Module '{}' has no class that inherits from customShifterMainStep".format(
238+
module_name
239+
)
240+
)
241+
242+
# Instantiate with the same step dictionary
243+
other_step = step_class(self._step_dict)
244+
245+
# Run setup
246+
other_step.setup()
247+
248+
# Register it in the step dictionary
249+
if other_step.name:
250+
self._step_dict[other_step.name] = other_step
251+
252+
# Run the step
253+
other_step.run()
254+
255+
self.log("Sub-step '{}' completed successfully".format(module_name))
256+
257+
return other_step
258+
259+
def get_or_create_setup_root(self, name=None):
260+
"""Get or create the setup root group for organizing custom step objects.
261+
262+
Args:
263+
name (str, optional): Custom name for the setup root.
264+
If None, uses "{self.name}_setup_grp"
265+
266+
Returns:
267+
PyNode: The setup root group
268+
269+
Example:
270+
setup_grp = self.get_or_create_setup_root()
271+
pm.parent(my_object, setup_grp)
272+
"""
273+
if not self.setup_root or not pm.objExists(self.setup_root):
274+
grp_name = name or "{}_setup_root".format(self.name)
275+
self.setup_root = pm.group(empty=True, name=grp_name)
276+
277+
# Try to parent under rig setup group if it exists
278+
if pm.objExists(
279+
self.mgear_run.setupWS
280+
):
281+
pm.parent(self.setup_root, self.mgear_run.setupWS)
282+
283+
return self.setup_root
284+
285+
def log(self, message, level="info"):
286+
"""Log a message with the step name prefix.
287+
288+
Args:
289+
message (str): The message to log
290+
level (str): Log level - "info", "warning", or "error"
291+
292+
Example:
293+
self.log("Processing controls")
294+
self.log("Missing component", level="warning")
295+
"""
296+
prefix = "[{}]".format(self.name) if hasattr(self, "name") else "[CustomStep]"
297+
full_message = "{} {}".format(prefix, message)
298+
299+
if level == "warning":
300+
pm.warning(full_message)
301+
elif level == "error":
302+
pm.error(full_message)
303+
else:
304+
print(full_message)
305+
60306
def setup(self):
61-
"""This function mus be re implemented for each custom step.
307+
"""This function must be re implemented for each custom step.
62308
63309
Raises:
64-
Exception: Description
310+
NotImplementedError: If not implemented in subclass
65311
"""
66-
raise NotImplemented("'setup' must be implemented")
312+
raise NotImplementedError("'setup' must be implemented")
67313

68314
def run(self):
69-
"""This function mus be re implemented for each custom step.
315+
"""This function must be re implemented for each custom step.
70316
71317
Raises:
72-
Exception: Description
318+
NotImplementedError: If not implemented in subclass
73319
"""
74-
raise NotImplemented("'run' must be implemented")
320+
raise NotImplementedError("'run' must be implemented")
75321

76322
def dup(self, source, name=None):
77323
"""Duplicate the source object and rename it

0 commit comments

Comments
 (0)