Instantly share code, notes, and snippets.
yamahigashi/gml_maya.menu.py Secret
Last active
November 25, 2023 11:07
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save yamahigashi/49bd1a8866c119ae78a166a8656a380e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
"""Module for generate menu from module.""" | |
import os | |
import re | |
import six | |
import sys | |
import inspect | |
import functools | |
import traceback | |
from textwrap import dedent | |
from Qt.QtWidgets import ( | |
QApplication, | |
QWidget, | |
QVBoxLayout, | |
QLabel, | |
QWidgetAction, | |
# QTextBrowser, | |
QTextEdit, | |
) | |
from Qt.QtGui import ( | |
QCursor, | |
QPixmap, | |
# QTextDocument, | |
) | |
from Qt.QtCore import ( | |
QObject, | |
Qt as C, | |
QEvent, | |
QPoint, | |
QTimer, | |
) | |
import maya.cmds as cmds | |
from gml_maya.util import jpn | |
from logging import getLogger, WARN, DEBUG, INFO, StreamHandler # pylint: disable=unused-import # noqa | |
# ---------------------------------------------------------------------------- | |
if sys.version_info > (3, 0): | |
from typing import TYPE_CHECKING | |
if TYPE_CHECKING: | |
from typing import ( | |
Optional, # noqa: F401 | |
Dict, # noqa: F401 | |
List, # noqa: F401 | |
Tuple, # noqa: F401 | |
Pattern, # noqa: F401 | |
Callable, # noqa: F401 | |
Any, # noqa: F401 | |
Text, # noqa: F401 | |
Generator, # noqa: F401 | |
Union # noqa: F401 | |
) | |
from types import ( # NOQA: F401 pylint: disable=unused-import | |
ModuleType, # noqa: F401 | |
) | |
# ---------------------------------------------------------------------------- | |
handler = StreamHandler() # pylint: disable=invalid-name | |
handler.setLevel(DEBUG) | |
logger = getLogger(__name__) # pylint: disable=invalid-name | |
logger.setLevel(DEBUG) | |
logger.setLevel(INFO) | |
logger.addHandler(handler) | |
logger.propagate = False | |
# ---------------------------------------------------------------------------- | |
__all__ = [ | |
"command_item", | |
"register_module_menu", | |
"deregister_module_menu", | |
"register_menu_help", | |
] | |
# ---------------------------------------------------------------------------- | |
# decorators | |
# ---------------------------------------------------------------------------- | |
def command_item(label, folder=None, divider=None, inviewmessage=False): | |
# type: (Text, Optional[Text], Optional[Text], bool) -> Callable[[Any], Any] | |
"""Decorator that converts a function into Maya command menu.""" | |
def decorator(func): | |
# type: (Callable[[Any], Any]) -> Callable[[Any], Any] | |
@functools.wraps(func) | |
def wrap(*_args, **_kwargs): | |
# type: (Any, Any) -> Any | |
amg = "" | |
try: | |
results = func() # type: ignore | |
try: | |
status_message = "successfully completed" | |
amg = """<p style="color:#FFFF22></p>{} finished <hl style="color:#00fcff>{}</hl>.""".format(jpn(label), status_message) | |
except UnicodeEncodeError: | |
amg = """<p style="color:#FFFF22></p>job successfully finished <hl>{}</hl>.<br>Please see the script editor log to more detail.""" | |
return results | |
except Exception: | |
traceback.print_exc() | |
traceback.print_stack() | |
amg = """<p style="color:#FFFF22></p> failed<hl>{}</hl>.<br>Please see the script editor log to more detail.""" | |
raise | |
finally: | |
if inviewmessage: | |
cmds.inViewMessage( | |
amg=amg, | |
pos="topCenter", | |
fade=True, | |
fadeStayTime=3400 | |
) | |
# these lines are sample for using not wrapped function but using class prototyping. | |
""" | |
CommandMenu = type( | |
'GmlCommandMenuItem', | |
(GmlCommandMenu,), | |
{'__doc__': func.__doc__} | |
) | |
CommandMenu.__name__ = func.__name__ | |
CommandMenu.__module__ = func.__module__ | |
CommandMenu.__call__ = staticmethod(func) | |
setattr(CommandMenu, "linenumber", inspect.getsourcelines(func)[1]) | |
setattr(CommandMenu, "folder", folder) | |
setattr(CommandMenu, "label", label) | |
setattr(CommandMenu, "divider", divider) | |
return CommandMenu() | |
""" | |
try: | |
setattr(wrap, "linenumber", inspect.getsourcelines(func)[1]) | |
except TypeError: | |
setattr(wrap, "linenumber", 10000) | |
setattr(wrap, "folder", folder) | |
setattr(wrap, "label", label) | |
setattr(wrap, "divider", divider) | |
setattr(wrap, "is_maya_menu_item", True) | |
return wrap | |
return decorator | |
# ---------------------------------------------------------------------------- | |
# exported functions | |
# ---------------------------------------------------------------------------- | |
def get_folder_name_for_module(module): | |
# type: (ModuleType) -> Text | |
"""Returns escaped packages name of given module""" | |
package_path = module.__name__ | |
safe_package_name = package_path.replace(".", "_") | |
return safe_package_name | |
def register_module_menu(module, menu_label, parent_menu_name): | |
# type: (ModuleType, Text, Text) -> None # noqa | |
"""Register command of given module under given parent""" | |
package_path = module.__name__ | |
safe_package_name = get_folder_name_for_module(module) | |
folder = get_menu_by_label(parent_menu_name, menu_label) | |
if not folder: | |
folder = cmds.menuItem( | |
safe_package_name, | |
label=jpn(menu_label), | |
subMenu=True, | |
tearOff=True, | |
parent=parent_menu_name | |
) # type: Any | |
members = [o for o in inspect.getmembers(module) if _is_menu_item(o[1])] | |
for klass_info in inspect.getmembers(module, inspect.isclass): | |
logger.debug(klass_info) | |
if not klass_info[1].__module__ == module.__name__: | |
continue | |
for method in inspect.getmembers(klass_info[1], inspect.ismethod): | |
logger.debug(method) | |
if _is_menu_item(method[1]): | |
members.append(method) | |
members.sort(key=_linenumber) | |
for func in members: | |
parent = folder | |
func_name = func[0] | |
label = func[1].label | |
sub_folder_label = func[1].folder | |
divider = func[1].divider | |
annotation = jpn(_get_annotation(func[1], as_one_line=True)) | |
if sub_folder_label: | |
sub_folder_parent = parent # type: Text | |
for hierarchy in sub_folder_label.split("/"): | |
# sub_folder_name = hierarchy.replace(" ", "_").replace("-", "_") | |
# sub_folder_name = "{}_{}".format(safe_package_name, sub_folder_name) | |
sub_folder = get_menu_by_label(sub_folder_parent, hierarchy) or "" # type: Any | |
if not sub_folder: | |
sub_folder = cmds.menuItem( | |
hierarchy, | |
label=jpn(hierarchy), | |
subMenu=True, | |
tearOff=True, | |
parent=sub_folder_parent | |
) | |
sub_folder_parent = sub_folder | |
parent = sub_folder_parent | |
if isinstance(divider, six.string_types): | |
cmds.menuItem( | |
dividerLabel=jpn(divider), | |
parent=parent, | |
divider=True | |
) | |
object_name = "{}_{}".format(package_path, func_name) | |
menu_item_path = cmds.menuItem( | |
object_name, | |
label=jpn(label), | |
parent=parent, | |
echoCommand=True, | |
annotation=annotation, | |
command=func[1], | |
) # type: Any | |
if annotation: | |
annotation = jpn(_get_annotation(func[1], as_one_line=False)) | |
register_menu_help(module, menu_item_path, annotation) | |
def get_menu_by_label(parent, label): | |
# type: (Text, Text) -> Optional[Text] | |
"""Returns menu item name by given label under given parent menu""" | |
for sub_menu in cmds.menu(parent, q=True, itemArray=True) or []: # type: ignore | |
sub_menu_label = cmds.menuItem(sub_menu, q=True, label=True) | |
if label == sub_menu_label: | |
return parent + "|" + sub_menu | |
def get_menu_by_name(parent, name): | |
# type: (Text, Text) -> Optional[Text] | |
"""Returns menu item name by given name""" | |
for sub_menu in cmds.menu(parent, q=True, itemArray=True) or []: # type: ignore | |
## exists = cmds.menuItem(sub_folder_name, exists=True) | |
if name == sub_menu: | |
return sub_menu | |
def deregister_module_menu(module): | |
# type: (ModuleType) -> None # noqa | |
"""Deregister commands given module from Maya menu.""" | |
if not module.__package__: | |
logger.error("missing module name %s", module) | |
return | |
safe_package_name = module.__package__.replace(".", "_") | |
members = [o for o in inspect.getmembers(module) if isinstance(o[1], GmlCommandMenu)] | |
for func in members: | |
func_name = func[0] | |
item_name = "{}_{}".format(safe_package_name, func_name) | |
try: | |
cmds.deleteUI(item_name, menuItem=True) | |
except RuntimeError: | |
logger.error("menu item: %s can not delete", item_name) | |
try: | |
cmds.deleteUI("{}".format(safe_package_name), menuItem=True) | |
except RuntimeError: | |
logger.error("menu folder: %s can not delete", safe_package_name) | |
# ---------------------------------------------------------------------------- | |
# Hover Help Widget | |
# ---------------------------------------------------------------------------- | |
class HoverTooltipWidget(QWidget): | |
"""Hover help widget""" | |
MAX_IMAGE_WIDTH = 600 | |
def __init__(self, message): | |
super(HoverTooltipWidget, self).__init__() | |
self.setWindowFlags( | |
# C.FramelessWindowHint | | |
# C.WindowDoesNotAcceptFocus | | |
C.WindowStaysOnTopHint | | |
C.ToolTip | |
) | |
layout = QVBoxLayout() | |
self.contents = QTextEdit(self) | |
layout.addWidget(self.contents) | |
# to avoid image display issue, use QLabel instead of QTextEdit with html | |
self.images = re.findall(r'<img alt="" src="([^"]+)" />', message) | |
message = re.sub(r'<p>(<img alt="" src="([^"]+)" />[ \n]*)+</p>', "", message) | |
self.contents.setHtml(message) | |
if len(self.images) == 0: | |
pass | |
elif len(self.images) == 1: | |
pixmap = QPixmap(self.images[0]) | |
width = min(pixmap.width(), self.MAX_IMAGE_WIDTH) | |
pixmap = pixmap.scaledToWidth(width) | |
img = QLabel(self) | |
img.setPixmap(pixmap) | |
layout.addWidget(img) | |
else: | |
self.rotate_images = ImageRotatingWidget(self, self.images) | |
layout.addWidget(self.rotate_images) | |
self.setLayout(layout) | |
self.hide() | |
def showEvent(self, event): | |
super(HoverTooltipWidget, self).showEvent(event) | |
self.adjust_contets_size() | |
def adjust_contets_size(self): | |
# type: () -> None | |
"""Adjust contents size""" | |
doc_height = self.contents.document().size().height() | |
doc_height = doc_height + 30 | |
self.contents.setFixedHeight(doc_height) | |
self.contents.adjustSize() | |
self.adjustSize() | |
# avoid garbage collection, store filters in global variable | |
__MENU_HELP_EVENT_FILTERS__ = [] # type: List[HoverEventFilter] | |
class HoverEventFilter(QObject): | |
"""Hover event filter. | |
This filter shows and hides given widget on hover events. | |
""" | |
def __init__(self, hover_widget): | |
super(HoverEventFilter, self).__init__() | |
self.hover_widget = hover_widget | |
self.delete_later = False | |
def eventFilter(self, watched, event): | |
# type: (QObject, QEvent) -> bool | |
"""Event filter.""" | |
global __MENU_HELP_EVENT_FILTERS__ # pylint: disable=global-statement | |
if any(( | |
event.type() == QEvent.HoverLeave, | |
event.type() == QEvent.Leave, | |
event.type() == QEvent.Hide, | |
event.type() == QEvent.FocusOut, | |
event.type() == QEvent.WindowDeactivate, | |
event.type() == QEvent.Enter, # Explicitly squash, quick focus change will trigger this unintentionally | |
)): | |
self.hover_widget.hide() | |
self.deleteLater() | |
self.delete_later = True | |
try: | |
__MENU_HELP_EVENT_FILTERS__.remove(self) | |
except ValueError: | |
pass | |
return True | |
if any(( | |
event.type() == QEvent.HoverEnter, | |
)): | |
if self.delete_later: | |
return True | |
self.hover_widget.show() | |
return True | |
return False | |
class ImageRotatingWidget(QWidget): | |
"""Rotating image widget""" | |
DURATION_MILLISECOND = 800 # 1.2 seconds | |
def __init__(self, parent, image_paths): | |
# type: (QWidget, List[Text]) -> None | |
super(ImageRotatingWidget, self).__init__(parent) | |
self.layout = QVBoxLayout(self) # type: QVBoxLayout | |
self.image_label = QLabel(self) | |
self.layout.addWidget(self.image_label) | |
self.image_paths = image_paths | |
self.current_image_index = 0 | |
# start timer | |
self.timer = QTimer(self) | |
self.timer.timeout.connect(self.update_image) | |
self.timer.start(self.DURATION_MILLISECOND) | |
self.update_image() | |
def update_image(self): | |
current_image_path = self.image_paths[self.current_image_index] | |
pixmap = QPixmap(current_image_path) | |
self.image_label.setPixmap(pixmap) | |
self.current_image_index = (self.current_image_index + 1) % len(self.image_paths) | |
def showEvent(self, event): | |
super(ImageRotatingWidget, self).showEvent(event) | |
self.current_image_index = 0 | |
current_image_path = self.image_paths[self.current_image_index] | |
pixmap = QPixmap(current_image_path) | |
self.image_label.setPixmap(pixmap) | |
self.current_image_index = (self.current_image_index + 1) % len(self.image_paths) | |
def get_maya_window(): | |
# type: () -> QWidget | |
for obj in QApplication.topLevelWidgets(): | |
if obj.objectName() == "MayaWindow": | |
return obj | |
else: | |
raise Exception("Maya main window not found") | |
def show_menu_tooltip_help(tootip_widget): | |
# type: (QWidget) -> None | |
"""Shows menu help widget on right side of given widget.""" | |
global __MENU_HELP_EVENT_FILTERS__ # pylint: disable=global-statement | |
event_filter = HoverEventFilter(tootip_widget) | |
under_cursor_widget = QApplication.widgetAt(QCursor.pos()) | |
under_cursor_widget.installEventFilter(event_filter) | |
__MENU_HELP_EVENT_FILTERS__.append(event_filter) | |
container_global_pos = under_cursor_widget.mapToGlobal(QPoint(0, 0)) | |
help_widget_x = container_global_pos.x() + under_cursor_widget.width() + 2 | |
help_widget_y = container_global_pos.y() - 20 | |
tootip_widget.move(help_widget_x, help_widget_y) | |
tootip_widget.show() | |
def register_menu_help(module, menu_item_path, message): | |
# type: (ModuleType, Text, Text) -> None | |
"""Registers menu help widget to Maya menu.""" | |
script_dir = os.path.dirname(os.path.abspath(module.__file__)) | |
html = render_markdown(message, script_dir) | |
tootip_widget = HoverTooltipWidget(html) | |
target_action, _ = get_qt_object_at_path(menu_item_path) | |
# the target_widget is not the same as the widget when tear off. | |
# so, we get widget by cursor position. and discard the target_widget now. | |
target_action.hovered.connect(lambda: show_menu_tooltip_help(tootip_widget)) | |
def get_qt_object_at_path(menu_item_path): | |
# type: (Text) -> Tuple[QWidgetAction, QWidget] | |
"""Retrieves QWidget from given Maya menu item name. | |
In Qt, menu item name is in format of "menu_item_path". | |
But in Maya, menu item name is in format of "menu|sub_menu|menu_item". | |
And the menu hierarchy represented in Qt is, | |
Case 1: Menu item is a menu | |
Parent (QMenu) | |
- MenuA action (QWidgetAction) # This contains some attributes for subsequently listed widgets | |
- MenuA widget (QWidget) # This is the actual widget that we want to get | |
- MenuB action (QWidgetAction) | |
- MenuB widget (QWidget) | |
... | |
Case 2: Menu item is a sub menu | |
Parent (QMenu) | |
- SubParent action (QWidgetAction) | |
- SubParent widget (QWidget) | |
- SubParent menu (QMenu) | |
- MenuA action (QWidgetAction) # This contains some attributes for subsequently listed widgets | |
- MenuA widget (QWidget) # This is the actual widget that we want to get | |
- MenuB action (QWidgetAction) | |
- MenuB widget (QWidget) | |
... | |
... | |
""" | |
def _find_recursive(parent, name, offset): | |
# type: (QObject, Text, int) -> Optional[Union[QWidget, QWidgetAction]] | |
for child in parent.children(): | |
if child.objectName() == name: | |
if isinstance(child, QWidgetAction): | |
index = child.parentWidget().children().index(child) | |
target_widget = child.parentWidget().children()[index + offset] | |
return target_widget | |
else: | |
return child | |
else: | |
x = _find_recursive(child, name, offset) | |
if x: | |
return x | |
path = menu_item_path.replace("MayaWindow|", "") # Remove MayaWindow prefix | |
# Because the object name may not be unique, | |
# we need to find the parent menu widget first by hierarchy. | |
parent = get_maya_window() | |
for menu in path.split("|")[0:-1]: | |
p = _find_recursive(parent, menu, 2) | |
if p is not None: | |
parent = p | |
else: | |
print("Menu widget not found: {}, {}".format(parent, menu)) | |
action = _find_recursive(parent, path.split("|")[-1], 0) | |
widget = _find_recursive(parent, path.split("|")[-1], 1) | |
if action is None or widget is None: | |
raise Exception("Menu widget not found: {}".format(menu_item_path)) | |
if not isinstance(action, QWidgetAction): | |
raise Exception("Menu widget not found: {}".format(menu_item_path)) | |
if not isinstance(widget, QWidget): | |
raise Exception("Menu widget not found: {}".format(menu_item_path)) | |
return action, widget | |
# ---------------------------------------------------------------------------- | |
# Internal functions | |
# ---------------------------------------------------------------------------- | |
def _linenumber(m): # pylint: disable=invalid-name | |
# type: (Tuple[Text, Any]) -> int | |
try: | |
return m[1].linenumber | |
except AttributeError: | |
return 999999 | |
def _generate_invoked_code(package_path, func_name): | |
return dedent(""" | |
import {} as x | |
x.{}() | |
""".format(package_path, func_name)) | |
def _is_menu_item(item): | |
# type: (Any) -> bool | |
try: | |
return hasattr(item, "is_maya_menu_item") | |
except AttributeError: | |
return False | |
# return isinstance(item, GmlCommandMenu) | |
def _get_annotation(func, as_one_line=False): | |
# type: (Callable[[Any], Any], bool) -> Text | |
doc = func.__doc__ | |
if not doc: | |
return "" | |
doc = dedent(doc) | |
if as_one_line: | |
lines = [x.strip() for x in doc.splitlines(True)] | |
doc = "".join(lines) | |
return doc | |
def render_markdown(md_text, script_directory): | |
# type: (Text, Text) -> Text | |
"""Renders markdown text to HTML.""" | |
import markdown | |
if sys.version_info[0] == 2: | |
try: | |
md_text = md_text.decode("cp932") | |
except UnicodeEncodeError: | |
pass | |
html = markdown.markdown(md_text) | |
resolved_html = re.sub( | |
r'(src|href)="/?(.*)"', | |
r'\1="{}/\2"'.format(script_directory.replace(os.sep, "/")), | |
html | |
) | |
resolved_html = resolved_html.replace(os.sep, "/") | |
return resolved_html | |
class GmlCommandMenu(object): | |
"""Base class that represents a command menu item.""" | |
pass | |
# ---------------------------------------------------------------------------- | |
# Inject menu tooltip to Maya menu | |
# ---------------------------------------------------------------------------- | |
def inject_annotation_as_menu_tooltip_to_maya_menu(main_menu_name): | |
# type: (Text) -> None | |
"""Injects help tooltip to Maya menu items.""" | |
import maya.mel as mel | |
import gml_maya.menu as m | |
# Get menu items | |
kids = [] # type: List[Text] | |
kids = cmds.menu(main_menu_name, q=True, itemArray=True) # type: ignore | |
if not kids: | |
command = cmds.menu(main_menu_name, q=True, postMenuCommand=True) # type: ignore | |
mel.eval(command) | |
kids = cmds.menu(main_menu_name, q=True, itemArray=True) # type: ignore | |
if not kids: | |
raise Exception("Failed to get menu items: {}".format(main_menu_name)) | |
if not isinstance(kids, list): | |
kids = [kids] | |
# Register menu help | |
for kid in kids: | |
path = main_menu_name + "|" + kid | |
annotation = cmds.menuItem(path, q=True, annotation=True) # type: Any | |
# if not isinstance(annotation, str): | |
# logger.error("Failed to get annotation: {}".format(path)) | |
# continue | |
try: | |
inject_annotation_as_menu_tooltip_to_maya_menu(path) | |
except RuntimeError: | |
pass | |
if not annotation.strip(): | |
continue | |
register_menu_help(m, path, annotation) | |
def inject_menu_tooltip_to_maya_menu(main_menu_name, label, message): | |
import gml_maya.menu as m | |
kids = cmds.menu(main_menu_name, q=True, itemArray=True) or [] # type: ignore | |
for kid in kids: | |
menu_item_path = main_menu_name + "|" + kid | |
l = cmds.menuItem(menu_item_path, q=True, label=True) | |
print(menu_item_path, l, label, label in l) | |
if label in l: | |
break | |
else: | |
raise Exception("Menu item not found: {}".format(label)) | |
register_menu_help(m, menu_item_path, message) | |
# ---------------------------------------------------------------------------- | |
# Debug | |
# ---------------------------------------------------------------------------- | |
def _debug_print_all_menus(): | |
def recursive(parent): | |
for child in parent.children(): | |
print(parent, child.objectName(), child) | |
recursive(child) | |
recursive(get_maya_window()) | |
if __name__ == "__main__": | |
# _debug_print_all_menus() | |
menu_items = [ | |
"MayaWindow|mainFileMenu", | |
# "MayaWindow|mainEditMenu", | |
# "MayaWindow|mainCreateMenu", | |
# "MayaWindow|mainSelectMenu", | |
# "MayaWindow|mainModifyMenu", | |
# "MayaWindow|mainDisplayMenu", | |
# "MayaWindow|mainWindowMenu", | |
# "MayaWindow|mainMeshMenu", | |
# "MayaWindow|mainEditMeshMenu", | |
# "MayaWindow|mainMeshToolsMenu", | |
# "MayaWindow|mainMeshDisplayMenu", | |
# "MayaWindow|mainCurvesMenu", | |
] | |
for item in menu_items: | |
inject_annotation_as_menu_tooltip_to_maya_menu(item) | |
message = r""" | |
###Add Divisions | |
Splits selected polygon components (edges or faces) into smaller components. Add Divisions is useful when you need to add detail to an existing polygon mesh in either a global or localized manner. Polygon faces can be divided into three-sided (triangles) or four-sided (quadrangles) faces. Edges can be subdivided so that the number of sides on a face is increased. | |
Select Edit Mesh > Add Divisions > to set the Add Divisions Options. | |
Note: The contents of the Add Divisions Options window changes depending on what component selection mode you are currently in. | |
""" | |
inject_menu_tooltip_to_maya_menu("MayaWindow|mainEditMeshMenu", "Add Divisions", message) | |
message = r""" | |
###Bevel | |
A Bevel expands each selected vertex and edge into a new face, rounding the edges of a polygon mesh. You can position these new faces at an offset from the original edge or scale them towards the original face center using the Bevel Options. | |
Performing a bevel operation on any mesh produces predictable results. In the following example, several corner edges are beveled. All of the beveled edges are equal in length, and the new segments are parallel. To circularize part of a mesh | |
 | |
 | |
""" | |
inject_menu_tooltip_to_maya_menu("MayaWindow|mainEditMeshMenu", "Bevel", message) | |
message = r""" | |
###Circularize components | |
Circularizing vertices, edges, or faces reorganizes them into a perfect circle using the selection's center as the circle's center. This is useful when you want to build structures that jut out of existing shapes. See Circularize command. | |
Note: Although circularize technically always creates a circular shape, you can use it to create other basic shapes by selecting a different number of components (see below). | |
To circularize part of a mesh | |
Select a set of vertices, edges, or faces on the mesh. | |
For vertices: You must select at least three vertices. The number of selected vertices will determine the shape of the circularized area (i.e. three creates a triangle, four creates a square, five creates a pentagon, etc). | |
For edges: Circularize is performed on each closed edge loop. | |
For faces: Circularize is performed on each set of faces that share at least one common edge. | |
Select Edit Mesh > Circularize from the Modeling menu set, or Shift + right-click and select Circularize Components. | |
The selected components are converted into a circle. | |
Adjust the options in the in-view editor to change the appearance of the circularization. In particular: | |
Use Normal Offset to push the new circle away or toward the original object. | |
Use Alignment to define whether or not the circle conforms to the original object surface. | |
Use Supporting Edges to convert resulting n-gons into tris. | |
Averages the directions of vertex normals. This affects the appearance of shaded polygons. | |
 | |
""" | |
inject_menu_tooltip_to_maya_menu("MayaWindow|mainEditMeshMenu", "Circularize", message) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import os | |
import maya.cmds as cmds | |
import gml_maya.menu as menu | |
import gml_maya.decorator as deco | |
@menu.command_item("Reset bindpose", divider="Skinning") | |
def reset_bindpose(): | |
""" | |
### Reset Bindpose | |
- select bone | |
- execute this command | |
 | |
""" | |
import maya.cmds as cmds | |
selection = cmds.ls(sl=True, long=True) | |
bones = cmds.ls(sl=True) or [] | |
bindposes = list(set(cmds.listConnections(bones, d=True, type="dagPose"))) | |
cmds.delete(bindposes) | |
cmds.dagPose(bp=True, save=True) | |
def register(parent): | |
# type: (Text) -> None | |
import personal_maya.modeling.menu as m | |
menu.register_module_menu(m, "Modeling", parent) | |
def deregister(): | |
# type: () -> None | |
import personal_maya.modeling.menu as m | |
menu.deregister_module_menu(m) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment