一、浅析规则模块
一个简单的Max规则模块架构如下图所示。所有的规则都保存在Artease平台上,项目管理员根据项目需要在平台上定制符合项目的规则方案,插件使用者将方案下载到本地进行解析执行。
Max插件的规则是模块化的,一个规则通常由一个或多个函数组成,举个例子:
def compare_pos(**argv):
import MaxPlus
obj_name = argv.get("object_name", "")
params = argv.get("rule", {})
condition = params.get("condition", "")
setting_pos = eval(condition)[1]
ret = MaxPlus.Core.EvalMAXScript('''
fn comparepos obj_name = (
obj = getObjByName(obj_name)
return obj.position as string
)
comparepos("%s")
''' % obj_name)
pos = eval(ret.Get())
flag = True
msg = pos
if pos:
for i in range(len(pos)):
flag = flag and abs(pos[i] - setting_pos[i]) < 0.0001
return flag, msg, True
Maxscript脚本的代码来源有两个地方,一是随着【规则方案】分发下来的检查规则内(如上面的fn comparePos obj_name
),另一种是随插件安装时自带的预置函数(如上面的fn getObjByName obj_name
),所有跟检查相关的Maxscript脚本都会在插件初始化的时候被加载进内存中。
一句话概况整个流程,插件在启动的时候拉取Artease平台的规则方案并加载,执行检查时通过包裹在Python内的Maxscript和内置的Maxscript脚本对资源进行检查,最后通过Python返回结果。
二、编写第一条规则
在开始正式编写规则前,我们先熟悉一下Artease后台的规则添加流程和目前支持的规则类型。下图是项目管理员视角下的Artease项目后台,我们建议对于组内定制化的规则,优先在本组内创建使用。点击【新建规则】,在弹出的规则创建卡中即可完成一条规则的创建。
下表介绍了在规则创建卡中每一条字段的实际意义,其中英文部分可以选择性填写,但如果不填写的话当语言环境切换至英文时则无法正常拉取到规则以及规则的英文描述。
字段 | 描述 |
---|---|
适用版本 | 规则可以执行的最低版本,仅提示,不作强制限制 |
对象 | 规则所属的类别,同时决定了规则在插件面板上的显示位置 |
分类 | 规则的方法类型 |
严重程度 | 规则的错误定级 |
desc | 规则的中文描述 |
condition | 检查条件 |
func_str | 规则函数(中文提示) |
en_desc | 规则的英文描述 |
en_func_str | 规则函数(英文提示) |
下表进一步解释了当前Max插件支持的检查规则类型。
规则类型 | 描述 |
---|---|
compare | 简单地比较一个属性 |
mcompare | 以xpath的方式比较一个属性 |
function | 自定义检查方法(批量检查) |
tool_function | 自定义检查方法(独立检查) |
tool_function_m | 执行内置maxscript方法(独立检查) |
py_file | 执行Python文件 |
ms_file | 执行maxscript文件 |
compare/mcompare
我们先从最简单的compare/mcompare方法入手。compare/mcompare都用于简单地比较一个基本属性,这类规则不需要用户自定义检查函数(func_str),只需要按照指定写法去规定condition字段即可。举个例子,如果我们想检查资源的的材质球命名是否符合以模型名开头 + _mat的规范:
type: compare
desc: 材质球命名为模型名+_mat
condition: ['material.name', '==', name + '_mat']
($.)A
的任意操作,比如:
name = $.name
material.name = $.material.name
position = $.position
desc: 模型的坐标大于[1, 2, 3]
condition: ['position', '>', [1, 2, 3]]
desc: 以model_开头的模型名字
condition: ['name', 're', '^model_']
name
模型名字、os_type
模型类型两个变量。
mcompare与compare相比除了使用xpath方式写规则外,比较方法还支持exec()方法。
function
function方法是最从常用的检查方法,它允许用户自由地编写脚本对max资源进行修改处理或检查,如果compare/mcompare无法满足你的检查要求,可以尝试使用这个方法。
基本使用
无参检查
还是用一个例子引入,熟悉一下整个规则函数的写法,如果我们想检查相机的视角是否为back视角:
def checkCameraView(**argv):
import MaxPlus
# 获取一些必要的检查信息
objName = argv.get("object_name", "")
# 执行Maxscript脚本
ret = MaxPlus.Core.EvalMAXScript(
"""
fn checkCameraView =
(
res = "view_back"
if viewport.getType() != #view_back then
(
res = viewport.getType() as string
)
res
)
checkCameraView()
"""
)
# 从Maxscript层获取结果
viewRes = ret.Get()
flag = True
msg = ""
if viewRes != "view_back":
msg = u"当前的摄像机视角为%s,不是view_back" % viewRes
flag = False
# 返回最终结果
return flag, msg, True
键 | 值 | 类型 |
---|---|---|
obj | 所有待检查的模型 | list(max_obj) |
object_name | 当前检查的模型名称 | str |
rule | 当前的检查规则信息 | dict |
context_data | 不同规则之间的上下文传递 | dict |
window | 插件主窗口实例 | QWidget |
AutoDesk官方为Python提供了2种模块去执行Maxscript代码,MaxPlus 和pymxs ,这里不做更多的对比,详见开发手册,需要指明的是MaxPlus模块从2021版本开始被移除,因此除非你的项目组使用的3dsMax低于2017版本,我们建议使用pymxs去执行函数字符串,写法与MaxPlus类似: |
||
|
||
检查完成之后,需要按规定返回结果。通常来说,只需要返回三个值: |
描述 | 类型 | 示例 |
---|---|---|
检查结果 | bool | True |
检查错误显示的信息 | str | 模型名称不包含_model |
是否支持批量检查 | bool | False表示一条错误信息会保存在多个模型中,反之则分开保存 |
##### 带参检查 | ||
为了使得检查规则更加的通用化,function方法支持传参以最大程度的复用规则在不同的情况下。回到这章最开始的那个例子,我们想去比较一个模型的位置是否符合要求: | ||
|
||
可以发现,相比于无参检查,规则从rule 字段中取出了规则函数的运行参数并将它保存在了setting_pos中。这样对于任意传入的规定坐标,我们都能将其与当前模型的坐标进行对比而无需二次修改规则函数,那么这个参数是在哪里传入的呢?当然是与规则函数名一起从平台上传入: |
||
|
||
#### 进阶使用 | ||
function方法提供了一些进阶使用的方式,为那些基本写法无法满足的规则开发者提供更加灵活的检查方式。在返回结果的第四个位置实际上存在一个字典,用于保存用户的一些自定义参数,目前我们开放的两个关键字分别是msg 和callback ,具体如下表: |
关键字 | 描述 | 类型 |
---|---|---|
msg | 用户自定义输出内容,无论结果正确与否都会输出 | str |
callback | 复现结果的函数变量名 | obj |
举个例子:
def myCallbackRule(**argv):
import pymxs
obj_name = argv.get("object_name", "")
params = argv.get("rule", {})
condition = params.get("condition", [])
def callback():
ret = pymxs.runtime.execute('''
fn selectMyFace obj_name =(
obj = getObjByName(obj_name)
obj.EditablePoly.SetSelection #Face #{1883}
)
selectMyFace "%s"
''' % obj_name)
return ret
callback()
user_content = {'callback': callback, 'msg': '这是自定义输出'}
return False, u'回调测试', True, user_content
callback字段的设计是为了解决在检查完成之后的某些操作影响了变动了检查结果,而导致需要二次检查的问题。比如,某个检查规则找出了具有问题的面片,并将其选中标记了出来,使用者在对单个错误面片进行修改后,其它的选中便消失了,如果检查逻辑比较耗时,那么再执行一次检查则会浪费不必要的时间上。因此,如果将结果显示逻辑独立出来,单独使用一个函数去执行结果显示操作就可以避开反复执行检查的问题。当然,callback的作用不止于此,规则开发者可以根据自己的需求去设计它。
tool_function/tool_function_m
tool_function与function的规则编写类似,但暂时不支持function内的进阶使用功能。它的目标规则是那些不需要频繁进行检查的规则,以及一些对资源做出修改的相对危险的规则,会以按钮的形式出现在插件面板上,我们同样用两个例子来解释它的写法与效果:
tool_function
无参检查
def resetXform(**argv):
import MaxPlus
obj_name = argv.get("object_name", "")
params = argv.get("rule", {})
condition = params.get("condition", [])
ret = MaxPlus.Core.EvalMAXScript('''
objarray = getcurrentselection()
for obj in objarray do
(
l = obj.baseobject as string
ResetXform obj
if l == "Editable Poly" do
ConvertTo obj Editable_Poly
if l == "Editable Mesh" do
ConvertTo obj Editable_Mesh
)
select objarray
''')
return True, u"\n", True
type: tool_function
desc: 重置模型
condition: ['resetXform']
带参检查
def delete_uv_channels(**argv):
import MaxPlus
obj_name = argv.get("object_name", "")
params = argv.get("rule", {})
condition = params.get("condition", {})
condition = eval(condition)
pri = condition[1][0][1]
if not obj_name.startswith(pri):
return True, u"\n", True
ret = MaxPlus.Core.EvalMAXScript(
'''
fn delete_uv_channels obj_name =
(
m_obj = getObjByName(obj_name)
if (classOf current_object) == Editable_Poly do
(
for i = 1 to polyOp.getNumMaps obj do
(
if (polyop.getMapSupport current_object i) do
polyop.setMapSupport current_object i false
)
)
if (classOf current_object) == Editable_mesh do
(
for i = 1 to meshOp.getNumMaps obj do
(
if (meshop.getMapSupport current_object i) do
meshop.setMapSupport current_object i false
)
)
)
delete_uv_channels "%s"
''' % obj_name
)
return True, u"\n", True
type: tool_function
desc: 清除前缀为%s的模型UV
condtion: ['delete_uv_channels', [['prefix_','col']]]
清除前缀为%s的模型UV
在插件端实际上显示的是清除前缀为col的模型UV
tool_function_m
tool_function_m用于直接执行max插件中内置的Maxscript
函数,如果需要执行内置的Python
脚本请直接使用tool_function方法。具体可调用的函数见API文档。
py_file/ms_file
为了使Artease for max插件能够兼容更多的第三方脚本,插件支持直接执行py和ms文件。根据需求,如果需要正确地将处理结果返回给max插件,请按照下列要求操作,否则直接执行即可。
ms_file
ms文件返回值需要为列表,列表中存在三类值:
描述 | 类型 | 例子 |
---|---|---|
结果flag | str | "True" |
模型名称 | str | "20995_p1_lods" |
错误msg | str | "资源不符合检查规则要求" |
一个例子:
fn check_name =(
ret = #()
for obj in selection do (
if obj.name != "Box001" then(
flag = "False"
msg = "20995_p1_lods"
append ret flag
append ret obj.name
append ret msg
)else(
flag = "True"
msg = ""
append ret flag
append ret obj.name
append ret msg
)
ret
)
)
check_name()
py_file
- Python文件必须要import artCheck.global_data,并在其中修改global_data中的值。
- global_data中有两个值:check_list(列表,长度不限,但必须为3的倍数,形式必须和ms文件一样为 [flag1,name1,msg1,flag2,name2,msg2,flag2=3,name3,msg3......])
- switch(在函数内必须把switch改为True,表示已修改global_data的值,该值为False时,检查结果不显示)
import artCheck.global_data
def check_name():
import MaxPlus
ret = pymxs.runtime.execute('''
fn check_name =(
ret = #()
for obj in selection do (
if obj.name != "Box001" then(
flag = "False"
msg = "20995_p1_lods"
append ret flag
append ret obj.name
append ret msg
)else(
flag = "True"
msg = ""
append ret flag
append ret obj.name
append ret msg
)
ret
)
)
''')
for i in range(len(ret)):
artCheck.global_data.check_list.append(ret[i])
artCheck.global_data.switch = True
return True
check_name()