maya-umbrella 0.3.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ # Import local modules
2
+ from maya_umbrella.core import Defender
3
+
4
+
5
+ __all__ = ["Defender"]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
maya_umbrella/core.py ADDED
@@ -0,0 +1,76 @@
1
+ # Import built-in modules
2
+ import logging
3
+ import re
4
+
5
+ # Import third-party modules
6
+ import maya.api.OpenMaya as om
7
+
8
+ # Import local modules
9
+ from maya_umbrella.filesystem import get_hooks
10
+ from maya_umbrella.filesystem import get_vaccines
11
+ from maya_umbrella.filesystem import load_hook
12
+
13
+
14
+ class Defender(object):
15
+ callback_ids = []
16
+ remove_callbacks = []
17
+ _bad_files = []
18
+ _vaccines = []
19
+ callback_maps = {
20
+ "afterOpen": om.MSceneMessage.kAfterOpen,
21
+ "afterImport": om.MSceneMessage.kAfterImport,
22
+ "afterImportReference": om.MSceneMessage.kAfterImportReference,
23
+ "afterLoadReference": om.MSceneMessage.kAfterLoadReference,
24
+ "beforeSave": om.MSceneMessage.kBeforeSave,
25
+ "beforeImport": om.MSceneMessage.kBeforeImport,
26
+ "beforeLoadReference": om.MSceneMessage.kBeforeLoadReference,
27
+ "beforeImportReference": om.MSceneMessage.kBeforeImportReference,
28
+ "beforeMayaExiting": om.MSceneMessage.kMayaExiting,
29
+ }
30
+
31
+ def __init__(self):
32
+ self.logger = logging.getLogger(__name__)
33
+ self.load_vaccines()
34
+
35
+ def load_vaccines(self):
36
+ for vaccine in get_vaccines():
37
+ self._vaccines.append(load_hook(vaccine).Vaccine(self.logger))
38
+
39
+ @property
40
+ def vaccines(self):
41
+ return self._vaccines
42
+
43
+ def run_hooks(self):
44
+ for hook in get_hooks():
45
+ self.logger.info("run_hooks: %s", hook)
46
+ load_hook(hook).hook(self.logger)
47
+
48
+ def setup(self):
49
+ for name, callback in self.callback_maps.items():
50
+ matched = re.search(r"(?P<name>(after|before))", name)
51
+ if matched:
52
+ method_name = matched.group("name")
53
+ self.logger.info("setup %s.", name)
54
+ self.callback_ids.append(
55
+ {name: om.MSceneMessage.addCallback(callback, getattr(self, "{0}_callback".format(method_name)))}
56
+ )
57
+
58
+ self.start()
59
+
60
+ def before_callback(self, *args, **kwargs):
61
+ self.logger.info("before_callback.")
62
+ for vaccine in self.vaccines:
63
+ vaccine.before_callback()
64
+ self.run_hooks()
65
+
66
+ def after_callback(self, *args, **kwargs):
67
+ self.logger.info("after_callback.")
68
+ for vaccine in self.vaccines:
69
+ vaccine.after_callback()
70
+ self.run_hooks()
71
+
72
+ def start(self):
73
+ for vaccine in self.vaccines:
74
+ self.logger.info("process for vaccine: {0}".format(vaccine.virus_name))
75
+ vaccine.process()
76
+ self.run_hooks()
@@ -0,0 +1,110 @@
1
+ # Import built-in modules
2
+ import glob
3
+ import importlib
4
+ import io
5
+ import os
6
+ import random
7
+ import shutil
8
+ import string
9
+
10
+
11
+ def this_root():
12
+ """Return the absolute path of the current file's directory."""
13
+ return os.path.abspath(os.path.dirname(__file__))
14
+
15
+
16
+ def safe_remove_file(file_path):
17
+ """Remove the file at the given path without raising an error if the file does not exist."""
18
+ try:
19
+ os.remove(file_path)
20
+ except (IOError, OSError):
21
+ pass
22
+
23
+
24
+ def safe_rmtree(path):
25
+ """Remove the directory at the given path without raising an error if the directory does not exist."""
26
+ try:
27
+ shutil.rmtree(path)
28
+ except (IOError, OSError):
29
+ pass
30
+
31
+
32
+ def read_file(path):
33
+ """Read the content of the file at the given path."""
34
+ with io.open(path, "r", encoding="utf-8") as file_:
35
+ content = file_.read()
36
+ return content
37
+
38
+
39
+ def write_file(path, content):
40
+ """Write the given content to the file at the given path."""
41
+ with io.open(path, "w", encoding="utf-8", newline="\n") as file_:
42
+ file_.write(content)
43
+
44
+
45
+ def patch_file(source, target, key_values, report_error=True):
46
+ """Modify the file at the source path with the given key value pairs and write the result to the target path.
47
+
48
+ If report_error is True, raise an IndexError if any of the keys are not found in the source file.
49
+ """
50
+ key_values = key_values if key_values else {}
51
+ found_keys = []
52
+ file_data = read_file(source)
53
+ for key, value in key_values.items():
54
+ before_ = file_data
55
+ file_data = file_data.replace(key, value)
56
+ if before_ != file_data:
57
+ found_keys.append(key)
58
+ write_file(target, file_data)
59
+
60
+ if report_error:
61
+ not_found = list(set(key_values.keys()) - set(found_keys))
62
+ if not_found:
63
+ raise IndexError("Not found: {0}".format(not_found))
64
+
65
+
66
+ def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
67
+ """Generate a random string of the given size using the given characters."""
68
+ return "".join(random.choice(chars) for _ in range(size))
69
+
70
+
71
+ def rename(src):
72
+ """Rename the file at the given path to a random name and return the new path."""
73
+ dst = os.path.join(os.path.dirname(src), "._{}".format(id_generator()))
74
+ try:
75
+ os.rename(src, dst)
76
+ except OSError:
77
+ return src
78
+ return dst
79
+
80
+
81
+ def load_hook(hook_file):
82
+ """Load the Python module from the given hook file."""
83
+ hook_name = os.path.basename(hook_file).split(".py")[0]
84
+ if hasattr(importlib, "machinery"):
85
+ # Python 3
86
+ # Import built-in modules
87
+ from importlib.util import spec_from_loader
88
+
89
+ loader = importlib.machinery.SourceFileLoader(hook_name, hook_file)
90
+ spec = importlib.util.spec_from_loader(loader.name, loader=loader)
91
+ module = importlib.util.module_from_spec(spec)
92
+ loader.exec_module(module)
93
+ else:
94
+ # Python 2
95
+ # Import built-in modules
96
+ import imp
97
+
98
+ module = imp.load_source(hook_name, hook_file)
99
+ return module
100
+
101
+
102
+ def get_hooks():
103
+ """Return a list of paths to all hook files in the 'hooks' directory."""
104
+ pattern = os.path.join(this_root(), "hooks", "*.py")
105
+ return [hook for hook in glob.glob(pattern) if "__init__" not in hook]
106
+
107
+
108
+ def get_vaccines():
109
+ pattern = os.path.join(this_root(), "vaccines", "*.py")
110
+ return [vaccine for vaccine in glob.glob(pattern) if "__init__" not in vaccine]
File without changes
@@ -0,0 +1,18 @@
1
+ # Import third-party modules
2
+ import maya.cmds as cmds
3
+ import maya.mel as mel
4
+
5
+
6
+ def hook(logger):
7
+ for plugin in ["Turtle.mll", "mayatomr.mll"]:
8
+ if cmds.pluginInfo(plugin, q=1, loaded=1):
9
+ cmds.unloadPlugin(plugin, f=1)
10
+ turtle_nodes = ["TurtleRenderOptions", "TurtleUIOptions", "TurtleBakeLayerManager", "TurtleDefaultBakeLayer"]
11
+ for node in turtle_nodes:
12
+ if cmds.objExists(node):
13
+ cmds.lockNode(node, lock=1)
14
+ cmds.delete(node)
15
+ if not cmds.about(query=1, batch=1):
16
+ shelves = cmds.tabLayout(mel.eval("$tmpVar=$gShelfTopLevel"), q=1, ca=1)
17
+ if "TURTLE" in shelves:
18
+ cmds.deleteUI("TURTLE", layout=1)
@@ -0,0 +1,29 @@
1
+ # Import third-party modules
2
+ import maya.cmds as cmds
3
+
4
+
5
+ def hook(logger):
6
+ unknown_node = cmds.ls(type="unknown")
7
+ unknown_plugin = cmds.unknownPlugin(query=True, l=True)
8
+ if unknown_node:
9
+ for nodeObj in unknown_node:
10
+ if cmds.objExists(nodeObj):
11
+ if cmds.referenceQuery(nodeObj, isNodeReferenced=True):
12
+ logger.warning("Node from reference, skip. {}".format(nodeObj))
13
+ continue
14
+ if cmds.lockNode(nodeObj, query=True)[0]:
15
+ try:
16
+ cmds.lockNode(nodeObj, lock=False)
17
+ except Exception as e:
18
+ logger.warning("The node is locked and cannot be unlocked. skip {}".format(nodeObj))
19
+ continue
20
+ try:
21
+ cmds.delete(nodeObj)
22
+ logger.warning("Delete node : {}".format(nodeObj))
23
+ except Exception as e:
24
+ pass
25
+
26
+ if unknown_plugin:
27
+ for plugObj in unknown_plugin:
28
+ cmds.unknownPlugin(plugObj, remove=True)
29
+ logger.warning("Delete plug-in : {}".format(plugObj))
@@ -0,0 +1,14 @@
1
+ # Import third-party modules
2
+ import maya.cmds as cmds
3
+
4
+
5
+ def hook(logger):
6
+ for model_panel in cmds.getPanel(typ="modelPanel"):
7
+
8
+ # Get callback of the model editor
9
+ callback = cmds.modelEditor(model_panel, query=True, editorChanged=True)
10
+
11
+ # If the callback is the erroneous `CgAbBlastPanelOptChangeCallback`
12
+ if callback == "CgAbBlastPanelOptChangeCallback":
13
+ # Remove the callbacks from the editor
14
+ cmds.modelEditor(model_panel, edit=True, editorChanged="")
@@ -0,0 +1,14 @@
1
+ # Import third-party modules
2
+ import maya.cmds as cmds
3
+ import maya.mel as mel
4
+
5
+
6
+ def hook(logger):
7
+ mel.eval('global proc onModelChange3dc(string $a){}')
8
+
9
+ try:
10
+ cmds.delete("fixCgAbBlastPanelOptChangeCallback")
11
+ except:
12
+ pass
13
+ script = "global proc CgAbBlastPanelOptChangeCallback(string $i){}"
14
+ mel.eval(script)
@@ -0,0 +1,80 @@
1
+ # Import built-in modules
2
+ import glob
3
+ import logging
4
+ import os
5
+
6
+ # Import third-party modules
7
+ import maya.cmds as cmds
8
+
9
+ # Import local modules
10
+ from maya_umbrella.filesystem import safe_remove_file
11
+ from maya_umbrella.filesystem import safe_rmtree
12
+
13
+
14
+ class BaseVaccine(object):
15
+ virus_name = None
16
+
17
+ _bad_files = []
18
+
19
+ def __init__(self, logger=None):
20
+ self._logger = logger or logging.getLogger(__name__)
21
+
22
+ @property
23
+ def user_app_dir(self):
24
+ return cmds.internalVar(userAppDir=True)
25
+
26
+ @property
27
+ def maya_install_root(self):
28
+ return os.environ["MAYA_LOCATION"]
29
+
30
+ @property
31
+ def user_script_path(self):
32
+ return cmds.internalVar(userScriptDir=True)
33
+
34
+ @property
35
+ def local_script_path(self):
36
+ return os.path.join(self.user_app_dir, "scripts")
37
+
38
+ @property
39
+ def bad_files(self):
40
+ return []
41
+
42
+ @property
43
+ def bad_nodes(self):
44
+ return []
45
+
46
+ @property
47
+ def bad_script_nodes(self):
48
+ return []
49
+
50
+ def collect(self):
51
+ self._bad_files.extend([temp_file for temp_file in glob.glob(os.path.join(self.local_script_path, "._*"))])
52
+
53
+ def remove_bad_files(self):
54
+ self.collect()
55
+ for file_ in self.bad_files:
56
+ if os.path.exists(file_):
57
+ if os.path.isfile(file_):
58
+ self._logger.info("Removing {}".format(file_))
59
+ safe_remove_file(file_)
60
+ else:
61
+ self._logger.info("Removing folder {}".format(file_))
62
+ safe_rmtree(file_)
63
+
64
+ def delete_bad_nodes(self):
65
+ for node in self.bad_nodes:
66
+ self._logger.info("Deleting %s", node)
67
+ cmds.lockNode(node, l=False)
68
+ cmds.delete(node)
69
+
70
+ def before_callback(self, *args, **kwargs):
71
+ self.remove_bad_files()
72
+ self.delete_bad_nodes()
73
+
74
+ def after_callback(self, *args, **kwargs):
75
+ self.remove_bad_files()
76
+ self.delete_bad_nodes()
77
+
78
+ def process(self):
79
+ self.before_callback()
80
+ self.after_callback()
File without changes
@@ -0,0 +1,19 @@
1
+ # Import built-in modules
2
+ import os.path
3
+
4
+ # Import local modules
5
+ from maya_umbrella.vaccine import BaseVaccine
6
+
7
+
8
+ class Vaccine(BaseVaccine):
9
+ virus_name = "pu tian tong qi"
10
+
11
+ def __init__(self, logger=None):
12
+ super(Vaccine, self).__init__(logger)
13
+
14
+ @property
15
+ def bad_files(self):
16
+ return [
17
+ os.path.join(self.local_script_path, "fuckVirus.py"),
18
+ os.path.join(self.local_script_path, "fuckVirus.pyc"),
19
+ ]
@@ -0,0 +1,65 @@
1
+ # Import built-in modules
2
+ import os.path
3
+
4
+ # Import third-party modules
5
+ import maya.cmds as cmds
6
+
7
+ # Import local modules
8
+ from maya_umbrella.filesystem import read_file
9
+ from maya_umbrella.filesystem import rename
10
+ from maya_umbrella.vaccine import BaseVaccine
11
+
12
+
13
+ class Vaccine(BaseVaccine):
14
+ virus_name = "zei jian kang"
15
+ _bad_files = []
16
+
17
+ def __init__(self, logger=None):
18
+ super(Vaccine, self).__init__(logger)
19
+
20
+ @property
21
+ def bad_files(self):
22
+ return [os.path.join(self.local_script_path, "vaccine.py"), os.path.join(self.local_script_path, "vaccine.pyc")]
23
+
24
+ def _get_nodes(self):
25
+ bad_script_nodes = []
26
+ for script_node in cmds.ls(type="script"):
27
+ if cmds.referenceQuery(script_node, isNodeReferenced=True):
28
+ continue
29
+ script_before_string = cmds.getAttr("{}.before".format(script_node))
30
+ script_after_string = cmds.getAttr("{}.after".format(script_node))
31
+ for script_string in [script_before_string, script_after_string]:
32
+ if not script_string:
33
+ continue
34
+ if "internalVar" in script_string or "userSetup" in script_string or "fuckVirus" in script_string:
35
+ self._logger.warning("script node {} has internalVar or userSetup or fuckVirus".format(script_node))
36
+ bad_script_nodes.append(script_node)
37
+ return bad_script_nodes
38
+
39
+ @property
40
+ def bad_nodes(self):
41
+ return self._get_nodes()
42
+
43
+ def check_usersetup_py(self):
44
+ for usersetup_py in [
45
+ os.path.join(self.local_script_path, "vaccine.py"),
46
+ os.path.join(self.user_script_path, "vaccine.py"),
47
+ os.path.join(self.local_script_path, "userSetup.py"),
48
+ os.path.join(self.user_script_path, "userSetup.py"),
49
+ ]:
50
+ if os.path.exists(usersetup_py):
51
+ data = read_file(usersetup_py)
52
+ if "petri_dish_path = cmds.internalVar(userAppDir=True) + 'scripts/userSetup.py" in data:
53
+ self._logger.warning("vaccine.py found : Infected by Malware!")
54
+ self._bad_files.append(rename(usersetup_py))
55
+
56
+ if (
57
+ "cmds.evalDeferred('leukocyte = vaccine.phage()')" in data
58
+ and "cmds.evalDeferred('leukocyte.occupation()')" in data
59
+ ):
60
+ self._logger.warning("userSetup.py : Infected by Malware!")
61
+ self._bad_files.append(rename(usersetup_py))
62
+
63
+ def before_callback(self, *args, **kwargs):
64
+ self.check_usersetup_py()
65
+ super(Vaccine, self).before_callback(args, kwargs)
@@ -0,0 +1,110 @@
1
+ # Import built-in modules
2
+ import glob
3
+ import os.path
4
+ import re
5
+
6
+ # Import third-party modules
7
+ import maya.cmds as cmds
8
+
9
+ # Import local modules
10
+ from maya_umbrella.filesystem import read_file
11
+ from maya_umbrella.filesystem import rename
12
+ from maya_umbrella.vaccine import BaseVaccine
13
+
14
+
15
+ class Vaccine(BaseVaccine):
16
+ virus_name = "virus2024429"
17
+ hik_regex = r"python\(\"import base64;\s*pyCode\s*=\s*base64\.urlsafe_b64decode\([\'\"].*?[\"\']\);\s*exec\s*\(\s*pyCode\s*\)\"\)\s*;"
18
+ _bad_files = []
19
+
20
+ def __init__(self, logger=None):
21
+ super(Vaccine, self).__init__(logger)
22
+
23
+ @property
24
+ def bad_files(self):
25
+ return self._bad_files
26
+
27
+ @property
28
+ def get_syssst_dir(self):
29
+ return os.path.join(os.getenv("APPDATA"), "syssst")
30
+
31
+ def _get_nodes(self):
32
+ bad_expression = []
33
+ for script_node in cmds.ls(type="script"):
34
+ if cmds.referenceQuery(script_node, isNodeReferenced=True):
35
+ continue
36
+ # check uifiguration
37
+ if cmds.objExists("{}.KGMScriptProtector".format(script_node)):
38
+ bad_expression.append(script_node)
39
+ # check vaccine
40
+ if "_gene" in script_node:
41
+ bad_expression.append(script_node)
42
+
43
+ return bad_expression
44
+
45
+ def check_usersetup_mel(self):
46
+ # check usersetup.mel
47
+ # C:/Users/hallong/Documents/maya/scripts/usersetup.mel
48
+ # C:/Users/hallong/Documents/maya/xxxx/scripts/usersetup.mel
49
+ for usersetup_mel in [
50
+ os.path.join(self.local_script_path, "usersetup.mel"),
51
+ os.path.join(self.user_script_path, "usersetup.mel"),
52
+ ]:
53
+ if os.path.exists(usersetup_mel):
54
+ data = read_file(usersetup_mel)
55
+ if "import base64; pyCode = base64.urlsafe_b64decode" in data:
56
+ self._bad_files.append(rename(usersetup_mel))
57
+
58
+ @property
59
+ def bad_nodes(self):
60
+ return self._get_nodes()
61
+
62
+ def before_callback(self, *args, **kwargs):
63
+ self._bad_files.extend([self.get_syssst_dir])
64
+ self.check_usersetup_mel()
65
+ self.fix_hik_files()
66
+ self.fix_script_job()
67
+ super(Vaccine, self).before_callback(args, kwargs)
68
+
69
+ def fix_hik_files(self):
70
+ pattern = os.path.join(self.maya_install_root, "resources/l10n/*/plug-ins/mayaHIK.pres.mel")
71
+ for hik_mel in glob.glob(pattern):
72
+ with open(hik_mel, "rb") as f:
73
+ data = f.read()
74
+ try:
75
+ check = re.findall(self.hik_regex, data)
76
+ except TypeError:
77
+ check = []
78
+ if len(check) > 0:
79
+ with open(hik_mel, "wb") as f:
80
+ f.write(re.sub(self.hik_regex, "", data))
81
+ self._logger.warning("Remove virus code from {}".format(hik_mel))
82
+
83
+ def fix_script_job(self):
84
+ virus_gene = [
85
+ "leukocyte",
86
+ "execute",
87
+ ]
88
+
89
+ def get_virus_script_jobs():
90
+ """Traverse the list of virus script job name.
91
+ Returns:
92
+ list: Malicious virus script job name.
93
+ """
94
+ return [
95
+ scriptjob
96
+ for scriptjob in cmds.scriptJob(listJobs=True)
97
+ for virus in virus_gene
98
+ if virus in scriptjob
99
+ ]
100
+
101
+ for script_job in get_virus_script_jobs():
102
+ script_num = int(script_job.split(":", 1)[0])
103
+ self._logger.info("Kill script job {}".format(script_job))
104
+ cmds.scriptJob(kill=script_num, force=True)
105
+
106
+ def after_callback(self, *args, **kwargs):
107
+ """After callback."""
108
+ self.check_usersetup_mel()
109
+ self.fix_hik_files()
110
+ super(Vaccine, self).after_callback(args, kwargs)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Hal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.1
2
+ Name: maya_umbrella
3
+ Version: 0.3.0
4
+ Summary: Check and fix maya virus.
5
+ Home-page: https://github.com/loonghao/maya_umbrella
6
+ License: MIT
7
+ Keywords: notifiers,python,Maya,dcc
8
+ Author: longhao
9
+ Author-email: hal.long@outlook.com
10
+ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 2
13
+ Classifier: Programming Language :: Python :: 2.7
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.6
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Project-URL: Documentation, https://github.com/loonghao/maya_umbrella
23
+ Project-URL: Repository, https://github.com/loonghao/maya_umbrella
24
+ Description-Content-Type: text/markdown
25
+
26
+ # maya_umbrella
27
+
28
+ Check and fix maya virus.
29
+
30
+ [![Nox](https://img.shields.io/badge/%F0%9F%A6%8A-Nox-D85E00.svg)](https://github.com/wntrblm/nox)
31
+ <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
32
+ [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
33
+ <!-- ALL-CONTRIBUTORS-BADGE:END -->
34
+
35
+
36
+
37
+
38
+
39
+ # 发开环境设置
40
+ python 版本 3.9
41
+
42
+ ```shell
43
+ pip install nox poetry
44
+ ```
45
+
46
+ # 开发调试
47
+
48
+ ```shell
49
+ nox -s maya-2020
50
+ ```
51
+
52
+ ## 在maya中测试
53
+ 通过`nox -s maya-xxx`, 启动maya.
54
+ nox会动态根据你本地安装得maya去注册nox session, 比如你本地安装了`maya-2020`,那么通过`nox -s maya-2018`
55
+
56
+ 启动maya后在脚本编辑器中执行下面得代码,就会动态的从`<repo>/tests/virus/`里面去open ma文件去进行测试.
57
+ ```python
58
+ import manual_test_in_maya
59
+
60
+ manual_test_in_maya.start()
61
+ ```
62
+
63
+ ## Contributors ✨
64
+
65
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
66
+
67
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
68
+ <!-- prettier-ignore-start -->
69
+ <!-- markdownlint-disable -->
70
+ <table>
71
+ <tbody>
72
+ <tr>
73
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/loonghao"><img src="https://avatars1.githubusercontent.com/u/13111745?v=4?s=100" width="100px;" alt="Hal"/><br /><sub><b>Hal</b></sub></a><br /><a href="https://github.com/loonghao/maya_umbrella/commits?author=loonghao" title="Code">💻</a></td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+
78
+ <!-- markdownlint-restore -->
79
+ <!-- prettier-ignore-end -->
80
+
81
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
82
+
83
+ This project follows the [all-contributors](https://allcontributors.org) specification.
84
+ Contributions of any kind are welcome!
85
+
@@ -0,0 +1,18 @@
1
+ maya_umbrella/__init__.py,sha256=XrGd2g7JVWk-K48K1SHETv1su1cDUj4rzeu36U8VOSc,88
2
+ maya_umbrella/__version__.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
3
+ maya_umbrella/core.py,sha256=YVMYLsWGZZup1FRgRGaMRj25jbopw8kByOSsjxMaLKg,2512
4
+ maya_umbrella/filesystem.py,sha256=U6G4lG4-kbTc2lMVlpg3Umd0u0HVkU2i-AuiEFegDXU,3434
5
+ maya_umbrella/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ maya_umbrella/hooks/delete_turtle.py,sha256=EYVXPYi7lBtlUqDFzS0NsftNomZtVBRD-uO705M4KN4,688
7
+ maya_umbrella/hooks/delete_unknown_plugin_node.py,sha256=JO0Y2FILrwP7rCW2dOqe_AMhe0OhMtZGHF3w2UAafew,1170
8
+ maya_umbrella/hooks/fix_model_panel.py,sha256=cJ65n0_OnFtyKYTnir6S3EluArb8qen1VoIQCpyHtCM,513
9
+ maya_umbrella/hooks/fix_on_model_change_3dc.py,sha256=iXXBZNiNYMnp8J0waBKPsEo_4TsTgD8zsV7mDbMM26E,341
10
+ maya_umbrella/vaccine.py,sha256=8ekUaa0xaizTOeDzDPtnsyVcj1puP_TUqXg5pdf2bGM,2045
11
+ maya_umbrella/vaccines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ maya_umbrella/vaccines/vaccine1.py,sha256=gWz7wl38Uy8s1gRSgcf5g9kN9WrD510zaLuh8FgxYVk,460
13
+ maya_umbrella/vaccines/vaccine2.py,sha256=dt0CHfHZHmbUCWF58jBpgAVrIikISdkTFrYMcLr82Dw,2659
14
+ maya_umbrella/vaccines/vaccine3.py,sha256=e6sM4JFst_yLkX4wwW7KeHIZkI5i9WoCER1uzUUcPJQ,3756
15
+ maya_umbrella-0.3.0.dist-info/LICENSE,sha256=tJf0Pz8q_65AjEkm3872K1cl4jGil28vJO5Ko_LhUqc,1060
16
+ maya_umbrella-0.3.0.dist-info/METADATA,sha256=DMn3Ff9evgj3noS6eh1Kh1o2UBeoZRPIljkmKGzVlzA,2818
17
+ maya_umbrella-0.3.0.dist-info/WHEEL,sha256=IrRNNNJ-uuL1ggO5qMvT1GGhQVdQU54d6ZpYqEZfEWo,92
18
+ maya_umbrella-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2.py3-none-any