imagemagick 用 C 和 C++ 编写的,非常小巧,而且运行速度很快~ 这里我没有使用 ZShotmask VP2 直接拍屏输出我要的信息,因为有些信息想要通过 imagemagick 叠加到图片上。 于是我想到可以利用 Pool 线程池的方式多线程后台调用命令行。
其中 from multiprocessing.dummy import Pool 可以导入 Python 隐藏的线程池。 这个用起来比起使用 threading 库要简单方便很多。 注: from multiprocessing import Pool 导入进程池, Maya 不太支持这个。 下面来个实例演示一下多线程调度后处理函数的好处。
默认 tasks 是配置了三中不同编译的选项,如果注释掉两个的话,那就可以直接在 VScode 实现 ctrl+shift+b 实现编译并运行。 教程里面主要 IDE 环境是使用 VScode 搭建的,可能会有人困惑,why not VS。 我很久以前开发 Maya C++ 就是使用 VS 进行开发的,说实话,IDE 隐藏了太多细节,一旦出错,反而是无头苍蝇,无从查起。 知乎回答 当然也同其他回答说得也对,用什么工具都无所谓,关键是懂得 C++ 的整个编译流程。
C++ 是一门很复杂的语言,像我是从 Python 开始进阶编程的。 当我将 Python 很多用法摸透之后,进入到 Python 底层,发现 C++ 还很多底层的内容等待我去学习(:з」∠) 那上面的视频,比较系统地总结了 C++ 从入门到进阶的各个不同阶段地内容,学习 C++ 有很清晰的整体图谱。 当然视频里面其实是介绍作者推出的课程的~
from maya import cmds window = cmds.window() cmds.setUITemplate("OptionsTemplate", pushTemplate=1)
column_layout = cmds.columnLayout() parent = cmds.radioButtonGrp('artAttrColorChannelChoices',q=1,parent=1) for control in cmds.layout(parent,q=1,childArray=1): cmds.control(control, e=1, p=column_layout) cmds.setUITemplate(popTemplate=1) cmds.showWindow(window)
from maya import cmds window = cmds.window() cmds.setUITemplate("OptionsTemplate", pushTemplate=1)
column_layout = cmds.columnLayout() parent = cmds.radioButtonGrp('artAttrColorChannelChoices',q=1,parent=1) for control in cmds.layout(parent,q=1,childArray=1): cmds.control(control, e=1, p=column_layout) if control == "artAttrColorChannelChoices": cmds.button(label="click me") cmds.setUITemplate(popTemplate=1) cmds.showWindow(window)
for control in cmds.layout(parent,q=1,childArray=1): cmds.control(control, e=1, p=column_layout) if control == "artAttrColorChannelChoices": cmds.button(label="click me") cmds.setUITemplate(popTemplate=1)
from maya import OpenMayaRender OpenMayaRender.MUIDrawManager # Error: AttributeError: file <maya console> line 2: 'module' object has no attribute 'MUIDrawManager' # from maya.api import OpenMayaRender OpenMayaRender.MUIDrawManager
import shiboken2 import maya.OpenMaya as om import maya.OpenMayaUI as omui
from PySide2.QtWidgets import QWidget from PySide2.QtCore import QObject
defactive_view(): """ return the active 3d view """ return omui.M3dView.active3dView()
defactive_view_wdg(): """ return the active 3d view wrapped in a QWidget """ view = active_view() active_view_widget = shiboken2.wrapInstance(long(view.widget()), QWidget) return active_view_widget
defpaintEvent(self, event): self.draw_shape(self.create_brush_cricle(), QtCore.Qt.white, 2) if self.is_press_B: self.draw_shape(self.create_brush_line(), QtCore.Qt.white, 2) self.draw_text(self._message_info) for curve, data in self.color_data.items(): self.draw_shape(data.get("points"), data.get("colors"), 10)
> 上面是绘制用到的 一些 API > 核心就是 draw_shape 里面如果传入了多个 color ,获取color每个顶点画一条渐变的线 > 多条线组合成圆形,由此有了衰变颜色的圆形曲线。
> 其他的绘制比如 绘制文字,Qt 有 drawText API > 绘制圆圈可以利用 sincos 数学函数来生成圆形的顶点进行绘制。
### 踩坑注意
> QtCore.QPoint 和 OpenMaya.MPoint 两者的 Y 轴坐标起始不一样,所以通过 M3dView 将世界坐标转换为屏幕坐标的时候需要额外的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defworld_to_view(position, invertY=True): """ convert the given 3d position to 2d viewport coordinates """ view = OpenMayaUI.M3dView.active3dView() arg_x = OpenMaya.MScriptUtil(0) arg_y = OpenMaya.MScriptUtil(0)
voidFTestLayoutWindowModule::StartupModule() { // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
这篇文章也是参考 sonictk 大佬的提供的 pyd 开发文章。 文章也提到之前的 hot reload 方案已经解决了很多 C++ 开发困难的问题。 然而还是有很多情况需要开发一个 python 的 C++ 模块实现 Maya C++ API 的 调用。 这个情况有点像是 Unreal 暴露 C++ API 到 Python 一样。
staticconstchar MAYA_PYTHON_C_EXT_DOCSTRING[] = "An example Python C extension that makes use of Maya functionality."; staticconstchar HELLO_WORLD_MAYA_DOCSTRING[] = "Says hello world!";
staticconstchar HELLO_WORLD_MAYA_DOCSTRING[] = "Says hello world!"; staticconstchar MAYA_PYTHON_C_EXT_DOCSTRING[] = "An example Python C extension that makes use of Maya functionality.";
# Import built-in modules from collections import defaultdict import json import os
# Import local modules import unreal
DIR = os.path.dirname(os.path.abspath(__file__))
defunreal_progress(tasks, label="进度", total=None): total = total if total elselen(tasks) with unreal.ScopedSlowTask(total, label) as task: task.make_dialog(True) for i, item inenumerate(tasks): if task.should_cancel(): break task.enter_progress_frame(1, "%s %s/%s" % (label, i, total)) yield item
# NOTE: 遍历命名为 Face 的 binding for binding in unreal_progress(binding_dict.get("Face", []), "导出 Face 数据"): # NOTE: 获取关键帧 channel 数据 keys_dict = {} for track in binding.get_tracks(): for section in track.get_sections(): for channel in unreal_progress(section.get_channels(), "导出关键帧"): ifnot channel.get_num_keys(): continue keys = [] for key in channel.get_keys(): frame_time = key.get_time() frame = frame_time.frame_number.value + frame_time.sub_frame keys.append({"frame": frame, "value": key.get_value()})
keys_dict[channel.get_name()] = keys
# NOTE: 导出 json name = binding.get_parent().get_name() export_path = os.path.join(DIR, "{0}.json".format(name)) withopen(export_path, "w") as wf: json.dump(keys_dict, wf, indent=4)
# Import built-in modules import json import os import traceback
# Import third-party modules import pymel.core as pm
DIR = os.path.dirname(os.path.abspath(__file__))
defprogress(seq, status="", title=""): pm.progressWindow(status=status, title=title, progress=0.0, isInterruptable=True) total = len(seq) for i, item inenumerate(seq): try: if pm.progressWindow(query=True, isCancelled=True): break pm.progressWindow(e=True, progress=float(i) / total * 100) yield item # with body executes here except: traceback.print_exc() pm.progressWindow(ep=1) pm.progressWindow(ep=1)
defmain():
# NOTE: 读取数据 withopen(os.path.join(DIR, "BP_metahuman_001.json"), "r") as rf: data = json.load(rf)
try: from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * from PySide2 import __version__ from shiboken2 import wrapInstance except ImportError: from PySide.QtCore import * from PySide.QtGui import * from PySide import __version__ from shiboken import wrapInstance
但是上面的代码需要 * 导入,并不符合我们的代码规范 使用 Qt.py 就可以轻松解决问题
1 2 3 4
from Qt import QtCore from Qt import QtGui from Qt import QtWidgets from Qt.QtCompat import wrapInstance
老版本的 Maya 使用 PySide 也会被映射到 PySide2 的调用规范上。
black 代码格式化
VScode 配置 black 工具 使用的 Python 必须 pip install black
defmodule_cleanup(module_name): """Cleanup module_name in sys.modules cache. Args: module_name (str): Module Name """ if module_name in sys.builtin_module_names: return packages = [mod for mod in sys.modules if mod.startswith("%s." % module_name)] for package in packages + [module_name]: module = sys.modules.get(package) if module isnotNone: del sys.modules[package] # noqa:WPS420
defmodule_cleanup(module_name): """Cleanup module_name in sys.modules cache. Args: module_name (str): Module Name """ if module_name in sys.builtin_module_names: return packages = [mod for mod in sys.modules if mod.startswith("%s." % module_name)] for package in packages + [module_name]: module = sys.modules.get(package) if module isnotNone: del sys.modules[package] # noqa:WPS420
classCustomFinder(object): def__init__(self): self.submodule_search_locations = [] self.has_location = False self.origin = None defcreate_module(self, spec): return self.load_module(spec.name) defexec_module(self, module): """Execute the given module in its own namespace This method is required to be present by importlib.abc.Loader, but since we know our module object is already fully-formed, this method merely no-ops. """
@add_short_name("pf") deftask_preflight(): """Run pre commit for all files. Returns: dict: doit config. """ command = ["poetry", "run", "pre-commit", "run", "-a"] return {"actions": [command], "verbosity": 2}
这样运行 doit 会识别到两个 task ,可以分别通过 doit pf 或者 doit preflight 触发指令
1 2 3
>doit list pf Run pre commit for all files. preflight Run pre commit for all files.
但是默认排序是按命名来的,如果命令很多就会混在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
>doit list b Run black format all python files. black Run black format all python files. d Run mkdocs serve. dd Run mike to deploy docs. docs Run mkdocs serve. docs_deploy Run mike to deploy docs. f Run `black` `isort`. format Run `black` `isort`. i Run isort format all python files. isort Run isort format all python files. l Run flakehell lint for all python files. lint Run flakehell lint for all python files. m Run mike serve. mike Run mike serve. pf Run pre commit for all files. preflight Run pre commit for all files. pt Run pytest. pytest Run pytest.
可以使用 doit list –sort=definition 的方式让排序变成创建顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
>doit list --sort=definition f Run `black` `isort`. format Run `black` `isort`. pf Run pre commit for all files. preflight Run pre commit for all files. b Run black format all python files. black Run black format all python files. i Run isort format all python files. isort Run isort format all python files. l Run flakehell lint for all python files. lint Run flakehell lint for all python files. pt Run pytest. pytest Run pytest. d Run mkdocs serve. docs Run mkdocs serve. m Run mike serve. mike Run mike serve. dd Run mike to deploy docs. docs_deploy Run mike to deploy docs.
initialized = signal('initialized') initialized is signal('initialized') sig = Signal()
可以使用匿名信号槽,也可以使用带名称的信号槽。
1 2 3 4 5 6 7 8 9
from blinker import signal send_data = signal('send-data') @send_data.connect defreceive_data(sender, **kw): print("Caught signal from %r, data %r" % (sender, kw)) return'received!' result = send_data.send('anonymous', abc=123) print(result) # 打印 [(<function receive_data at 0x000002A3328D4DC8>, 'received!')] # 打印 Caught signal from 'anonymous', data {'abc': 123}