|
| 1 | +# Copyright (C) 2025 David Cattermole. |
| 2 | +# |
| 3 | +# This file is part of mmSolver. |
| 4 | +# |
| 5 | +# mmSolver is free software: you can redistribute it and/or modify it |
| 6 | +# under the terms of the GNU Lesser General Public License as |
| 7 | +# published by the Free Software Foundation, either version 3 of the |
| 8 | +# License, or (at your option) any later version. |
| 9 | +# |
| 10 | +# mmSolver is distributed in the hope that it will be useful, |
| 11 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | +# GNU Lesser General Public License for more details. |
| 14 | +# |
| 15 | +# You should have received a copy of the GNU Lesser General Public License |
| 16 | +# along with mmSolver. If not, see <https://www.gnu.org/licenses/>. |
| 17 | +# |
| 18 | + |
| 19 | +from __future__ import absolute_import |
| 20 | +from __future__ import division |
| 21 | +from __future__ import print_function |
| 22 | + |
| 23 | +import maya.cmds |
| 24 | + |
| 25 | +import mmSolver.logger |
| 26 | +import mmSolver.utils.time as time_utils |
| 27 | +import mmSolver.utils.python_compat as pycompat |
| 28 | +import mmSolver.utils.node as node_utils |
| 29 | +import mmSolver.ui.channelboxutils as channelbox_utils |
| 30 | +import mmSolver.tools.attributecurvefilterpops.constant as const |
| 31 | + |
| 32 | + |
| 33 | +LOG = mmSolver.logger.get_logger() |
| 34 | + |
| 35 | + |
| 36 | +def get_frame_range(frame_range_mode, custom_start_frame, custom_end_frame): |
| 37 | + assert isinstance(frame_range_mode, pycompat.TEXT_TYPE) |
| 38 | + assert frame_range_mode in const.FRAME_RANGE_MODE_VALUES |
| 39 | + assert isinstance(custom_start_frame, pycompat.INT_TYPES) |
| 40 | + assert isinstance(custom_end_frame, pycompat.INT_TYPES) |
| 41 | + LOG.debug("frame_range_mode: %r", frame_range_mode) |
| 42 | + |
| 43 | + frame_range = None |
| 44 | + if frame_range_mode == const.FRAME_RANGE_MODE_TIMELINE_INNER_VALUE: |
| 45 | + frame_range = time_utils.get_maya_timeline_range_inner() |
| 46 | + elif frame_range_mode == const.FRAME_RANGE_MODE_TIMELINE_OUTER_VALUE: |
| 47 | + frame_range = time_utils.get_maya_timeline_range_outer() |
| 48 | + elif frame_range_mode == const.FRAME_RANGE_MODE_CUSTOM_VALUE: |
| 49 | + frame_range = time_utils.FrameRange(custom_start_frame, custom_end_frame) |
| 50 | + else: |
| 51 | + LOG.error("Invalid frame range mode: %r", frame_range_mode) |
| 52 | + return frame_range |
| 53 | + |
| 54 | + |
| 55 | +def _get_node_attrs_for_selected_keyframes(): |
| 56 | + """Get animCurve nodes from the selected keyframes (in the graph |
| 57 | + editor). |
| 58 | + """ |
| 59 | + selected_anim_curve_nodes = ( |
| 60 | + maya.cmds.keyframe(query=True, selected=True, name=True) or [] |
| 61 | + ) |
| 62 | + node_attrs = set() |
| 63 | + for anim_curve_node in selected_anim_curve_nodes: |
| 64 | + node_attr = anim_curve_node + '.output' |
| 65 | + conns = ( |
| 66 | + maya.cmds.listConnections( |
| 67 | + node_attr, destination=False, source=False, plugs=True |
| 68 | + ) |
| 69 | + or [] |
| 70 | + ) |
| 71 | + node_attrs |= set(conns) |
| 72 | + return list(sorted(node_attrs)) |
| 73 | + |
| 74 | + |
| 75 | +def _get_selected_graph_editor_outliner_node_attributes(): |
| 76 | + """ |
| 77 | + Get the attributes from the selected graph editor outliner. |
| 78 | + """ |
| 79 | + # NOTE: Do we need to support multiple graph editors? |
| 80 | + connection_object = 'graphEditor1FromOutliner' |
| 81 | + objects = ( |
| 82 | + maya.cmds.selectionConnection(connection_object, query=True, object=True) or [] |
| 83 | + ) |
| 84 | + |
| 85 | + node_attrs = set() |
| 86 | + for obj in objects: |
| 87 | + if not isinstance(obj, pycompat.TEXT_TYPE): |
| 88 | + continue |
| 89 | + if '.' in obj: |
| 90 | + node_attrs.add(obj) |
| 91 | + return list(sorted(node_attrs)) |
| 92 | + |
| 93 | + |
| 94 | +def _get_selected_channelbox_attributes(): |
| 95 | + """Get the selected attributes from the channel box.""" |
| 96 | + name = channelbox_utils.get_ui_name() |
| 97 | + attrs = maya.cmds.channelBox(name, query=True, selectedMainAttributes=True) or [] |
| 98 | + return attrs |
| 99 | + |
| 100 | + |
| 101 | +def get_selected_node_attrs(nodes): |
| 102 | + """ |
| 103 | + Get the selected node attributes, from the Graph Editor or |
| 104 | + Channel Box. |
| 105 | +
|
| 106 | + A universal function to get whatever the user has "selected" for |
| 107 | + the animation curve. This includes looking at the channel box, or |
| 108 | + using the selected curves in the Graph Editor, etc. |
| 109 | + """ |
| 110 | + node_attrs = _get_node_attrs_for_selected_keyframes() |
| 111 | + if len(node_attrs) > 0: |
| 112 | + return node_attrs |
| 113 | + |
| 114 | + node_attrs = _get_selected_graph_editor_outliner_node_attributes() |
| 115 | + if len(node_attrs) > 0: |
| 116 | + return node_attrs |
| 117 | + |
| 118 | + channelbox_attrs = _get_selected_channelbox_attributes() |
| 119 | + if len(channelbox_attrs) == 0: |
| 120 | + LOG.warn("Please select at least 1 attribute in the Channel Box.") |
| 121 | + return |
| 122 | + |
| 123 | + # Combine nodes with attributes |
| 124 | + node_attrs = [] |
| 125 | + for node in nodes: |
| 126 | + for attr in channelbox_attrs: |
| 127 | + if node_utils.attribute_exists(attr, node): |
| 128 | + node_attr = '{}.{}'.format(node, attr) |
| 129 | + node_attrs.append(node_attr) |
| 130 | + |
| 131 | + return node_attrs |
| 132 | + |
| 133 | + |
| 134 | +def get_attribute_anim_curves(node_attrs): |
| 135 | + """ |
| 136 | + Return anim curve nodes from attributes. |
| 137 | +
|
| 138 | + :param node_attrs: Node attribute names. |
| 139 | + :rtype: [] |
| 140 | + """ |
| 141 | + assert len(node_attrs) > 0 |
| 142 | + assert isinstance(node_attrs, (list, set)) |
| 143 | + anim_curve_node = maya.cmds.listConnections(node_attrs, type='animCurve') or [] |
| 144 | + |
| 145 | + anim_curve_nodes = [] |
| 146 | + if isinstance(anim_curve_node, pycompat.TEXT_TYPE): |
| 147 | + anim_curve_nodes = [anim_curve_node] |
| 148 | + elif isinstance(anim_curve_node, list): |
| 149 | + anim_curve_nodes = anim_curve_node |
| 150 | + return anim_curve_nodes |
| 151 | + |
| 152 | + |
| 153 | +def filter_curves_pops(anim_curve_nodes, start_frame, end_frame, threshold): |
| 154 | + """ |
| 155 | + Filter pops from curves on the attributes on nodes. |
| 156 | +
|
| 157 | + :param anim_curve_nodes: AnimCurve nodes to filter. |
| 158 | + :param start_frame: Start frame to filter. |
| 159 | + :param end_frame: End frame to filter. |
| 160 | + :param threshold: The threshold to use for pop detection. |
| 161 | + """ |
| 162 | + assert len(anim_curve_nodes) > 0 |
| 163 | + assert isinstance(start_frame, pycompat.INT_TYPES) |
| 164 | + assert isinstance(end_frame, pycompat.INT_TYPES) |
| 165 | + assert isinstance(threshold, float) |
| 166 | + |
| 167 | + maya.cmds.mmAnimCurveFilterPops( |
| 168 | + anim_curve_nodes, |
| 169 | + startFrame=start_frame, |
| 170 | + endFrame=end_frame, |
| 171 | + threshold=threshold, |
| 172 | + ) |
| 173 | + return |
0 commit comments