1. A Brief Analysis of the Rules Module
A simple rule module framework in Max is shown in the diagram below. All of the rules are saved on the ArtEase platform; the Project Manager customizes them on the platform within the project's rules program, according the project's requirements; and the plug-in user downloads the rules program locally to analyse and execute it.
The rules of the Max plug-in are modular, and a rule is usually composed of one or more functions. For example:
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
The code for the Maxscript script comes from two sources, the first is within the instruction rules that are assigned according to [Rule Scheme] ('fn comparePos obj_name' shown above), and the other is the pre-set functions athat come preinstalled with the plug-in installation ('fn getObjByName obj_name' shown above). All Maxscript scripts related to this inspection will be loaded into the memory when the plug-in is initialized.
To sum up the whole process in a single sentence: When the plug-in launches, it pulls the rule scheme on the ArtEase platform and loads it; during inspection it checks resources through Maxscript wrapped in Python and built-in Maxscript script; finally, it uses Python to return the results.
2. Compiling the First Rule
Before starting to compile rules formally, we will first briefly familiarize ourselves with the rule-adding process and currently supported rule types on ArtEase's back end. The image below shows an ArtEase project back end from a Project Manager's perspective. We recommend that when customizing rules for a group, they first create and use them within this group. Click [New Rule] and create a rule in the rule creation card pop-up.
The table below explains the definition of each field in the rule creation card. Filling in the English section is optional, but if it is not filled in it will not be possible to pull rules or their English description normally when the language interface is switched to English.
Field | Description |
---|---|
Applicable Version | The oldest version that can be carried out by a rule. It is just a reminder, not a compulsory limit. |
Object | The category a rule belongs to, which determines its display position on the plug-in panel. |
Category | The method category of the rule |
Severity | The error ranking of the rule |
desc | Chinese rule description |
condition | Inspection condition |
func_str | Rule function (Chinese reminder) |
en_desc | English rule description |
en_func_str | Rule function (English reminder) |
The table below goes on to explain the rule types currently supported in the Max plug-in.
Rule type | Description |
---|---|
compare | simply compares a property |
mcompare | compares a property using xpath |
function | custom inspection method (batch inspection) |
tool_function | custom inspection method (independent inspection) |
tool_function_m | run built-in maxscript method (indpendent inspection) |
py_file | run Python file |
ms_file | run maxscript file |
compare/mcompare
We'll start with the simplest method, compare/mcompare. Compare/mcompare are both used to simply compare a basic attribute. This type of rule does not require user-defined inspection functions (func_str), it only needs to specify the condition field according to the designated writing method. To give an example, if we wanted to check that the resource's shader ball was named in compliance with the standard that it starts with the model name + _mat:
type: compare
desc: shader ball named as the model +_mat
condition: ['material.name', '==', name + '_mat']
name = $.name
material.name = $.material.name
position = $.position
desc: The model's coordinates are greater than [1, 2, 3]
condition: ['position', '>', [1, 2, 3]]
desc: use model names starting with model_
condition: ['name', 're', '^model_']
function
function is the most frequently used inspection method. It allows users to freely compile scripts to modify or inspect Max resources. If compare/mcompare are unable to fulfil your inspection needs, you can try this method.
Basic Use
No Parameter Checking
To introduce this with another example and familiarize ourselves with hw to write an entire rule function, if we want to check whether the camera angle is a back angle:
def checkCameraView(**argv):
import MaxPlus
# Obtain necessary inspection information
objName = argv.get("object_name", "")
# Run Maxscript script
ret = MaxPlus.Core.EvalMAXScript(
"""
fn checkCameraView =
(
res = "view_back"
if viewport.getType() != #view_back then
(
res = viewport.getType() as string
)
res
)
checkCameraView()
"""
)
# Get results from Maxscript layer
viewRes = ret.Get()
flag = True
msg = ""
if viewRes != "view_back":
msg = u"The current viewing angle of the camera is %s,not view_back" % viewRes
flag = False
# Return final result
return flag, msg, True
Key | Value | Type |
---|---|---|
obj | all models awaiting inspection | list(max_obj) |
object_name | name of model currently being inspected | str |
rule | details of rule currently being checked | dict |
context_data | context transmission between different rules | dict |
window | plug-in main windown instance | QWidget |
AutoDesk has officially provided Python with two modules for running Maxscript code, 'MaxPlus' and 'pymxs'. No further comparisons are made here, for details see [Developer Handbook] (https://help.autodesk.com/view/3DSMAX/2017/ENU/?guid=__developer_executing_maxscript_from_python_html). It should be mentioned that the MaxPlus module has been removed starting from the 2021 version, so unless your project group uses a version of 3dsMax below the 2017 version, we reccommend that you use pymxs to run the function string; it is written similarly to MaxPlus: | ||
|
||
After the inspection, the results must be returned according to requirements. Usually this requires just three values: |
Description | Category | Example |
---|---|---|
inspection result | bool | True |
inspection error message | str | model name does not contain _model |
whether batch inspection is supported | bool | False indicates that nan error message will be saved in multiple models, otherwise it will save separately |
##### Parameter Checking | ||
To make inspection rules more universalized, function supports transmitting parameters to allow the greatest repeated use of rules under different circumstances. Returning to the example used at the beginning of this section, we want to compare whether a model's position meets requirements: | ||
|
||
We can see that, in contrast to checking without parameters, the rule extracted the the rule function's operational parameters from the 'rule' field and saved it in setting_pos. Thus, we can compare any specified coordinates that are imported with the current model's coordinates without needing to modify the rule function a second time. Where, then, is this parameter imported? Of course, it is imported from the platform along with the rule function name: | ||
|
||
#### Advanced Use | ||
Function has provided some advanced uses, providing more flexible inspection methods for rule developers who require writing methods beyond the basics. In the fourth position of the returned results, there is a glossary which is used to save user-defined parameters. Currently, the two keywords that we have open are 'msg' and 'callback', as shown in the table below: |
Keyword | Description | Category |
---|---|---|
msg | User-defined output content, output regardless of whether or not the result is correct | str |
callback | function variable name of repeated results | obj |
An example:
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': 'this is user-determined output'}
return False, u'callback test', True, user_content
The callback field is designed to resolve an issue in which, after the inspection is complete, certain operations cause a change in the inspection result resulting in the need for a second inspection. For instance, a certain inspection rule found faces that had mistakes and marked them as selected. After users altered a single face error, the other selections disappeared. If the inspection logic is fairly time consuming, then carrying out another inspection will waste time unnecessarily. Therefore, if the result display logic appears independently, and independently uses a function to carry out the result display operation then tbe issue of repeated inspections can be avoided. Of course, callback is not only used for this and rule developers can design it according to their needs.
tool_function/tool_function_m
Rule compilation with tool_function is similar to that of function, but it does not currently support the advanced uses that function does. It is aimed at rules that do not need to be checked frequently and some relatively risky rules that alter resources, which appear on the plug-in panel as buttons. Once again, we will use two examples to demonstrate how it is written and its effect:
tool_function
Checking without parameter
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: reset model
condition: ['resetXform']
Parameter Checking
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: clear model UV prefaced with %s
condtion: ['delete_uv_channels', [['prefix_','col']]]
tool_function_m
Tool_function_m is used to run the Max plug-in's built-in 'Maxscript' function directly. If you need to run the built-in 'Python' script, please use the tool_function method directly. For specific functions that can be called, see the API document.
py_file/ms_file
In order to make the Artease for Max plug-in compatible with more third-party scripts, it supports directly running py and ms files. According to your requirements, if you need to correctly process the return of results to the Max plug-in, please operate it according to the following requirements; otherwise, you can run it directly.
ms_file
The return value of ms files must be a list, on which are three different kinds of value:
Description | Category | Example |
---|---|---|
Result flag | str | "True" |
Model name | str | "20995_p1_lods" |
Error msg | str | "resource does not meet inspection rule requirements" |
An example:
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 files must import artCheck.global_data,and modify the value of global_data in it.
- global_data has two values: check_list (list, unlimited length, but must be a multiple of 3, its form must be the same as an ms file: [flag1,name1,msg1,flag2,name2,msg2,flag2=3,name3,msg3......])
- switch (the switch must be changed to True within the function, indicating that the value of global_data has been modified, when this value is False, the inspection result will not be shown)
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()