Skip to content

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 example above is a simple Max inspection rule, consisting of a Python function called compare_pos. This function exists in [Rule Scheme], and during the resource inspection process the function will be repeatedly called to check every selected submodel and return the results of the inspection. However if we examine the details, the Python function does not actually contact *.max directly, but rather is only responsible for transferring and returning surface variables. What actually has an effect on the inspection is the Maxscript script wrapped inside this Python function.

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']
The content of condition can be divided into three parts, [A (max object attribute), B (comparison method), C (inspection condition)],the content of max object attribute is actually some keywords set aside in maxscript. It is based on the submodel currently being inspected, so you can understand it as any '($.)A' operation. For instance:
name = $.name
material.name = $.material.name
position = $.position 
Comparison method is a conditional operation method privided by the plug-in, and supports regular operations in addition to Python's conditional operators. For example:
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_']
Inspection condition is used to compare the conditions of the rules' object attributes. It supports list, tuple, dict, and unicode. It cannot support fetch attribute operations like object attributes can, but users can directly call the variables 'name' model name and 'os_type' model type. mcompare, compared with compare, uses xpath to write rules in addition to its comparison method supporting the exec() method.

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
The idea of the whole inspection process is to obtain the necessary information for the inspection, transmit it and run it in the Marxscript layer, export the Maxscript data to the Python layer for further evaluation, and return the final inspection result. The formal parameter argv provides some parameters that are used for inspection shown in the following table. The list will receive real-time updates, making it convenient for developers to call:

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:
import pymxs
ret = pymxs.runtime.execute('''
    Your maxscript codes
'''
)
print ret
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:
def compare_pos(**argv):
    import pymxs

    obj_name = argv.get("object_name", "")
    params = argv.get("rule", {})
    condition = params.get("condition", "")
    setting_pos = eval(condition)[1]
    ret = pymxs.runtime.execute('''
        fn comparepos obj_name = (
        obj = getObjByName(obj_name)
        return obj.position as string
    )
    comparepos("%s")
    ''' % obj_name)
    pos = eval(ret)

    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
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:
type: function
desc: initial position must be x, y, z
condition: ['compare_pos', [0, 0, 0]]
#### 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
If the msg field is filled in, then during the inspection result output other content will be omitted:

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']]]
Please note the difference between paramater checking with tool_function and function: function: ['compare_pos', [0, 0, 0]] tool_function: ['delete_uv_channels',[['prefix_','col']]] In both of them, the first position in the outer list is the function name and the second position is the parameter, but tool_function must wrap two layers of lists and simultaneously the number of actual parameters imported must be two, in which the second parameter can take the form of the variable shown in desc, as shown in the example above, 'clear model UV prefaced with %s' is actually displayed as 'clear model UV prefaced with col' in the plug-in.

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()