前言

  Abc助手其实最开始是师兄跟我提的。
  我记得刚到公司的第一天,师兄就让我去研究如何实现批量将材质信息上到每个面上。
  这就为这个插件的诞生慢下了伏笔。

  其实就我自己而言,我当时并不太理解师兄想要实现的效果。
  不过就是这么凑巧的,我们的三维动画实训也遇到了Alembic导出到Houdini的材质问题。
  当时咨询了师兄之后才知道,原来问题都是一样的。
  而这些信息从模型信息写为模型的面信息就是出于这个考虑。

  师兄提供了一篇很好的推文,上面详细地说明了这个导出的解决方案。
  我就是基于这篇推文的思路进行开发的。

界面开发

  这次是首次尝试使用 Qt Designer 来开完整的插件,不过之前也用PyQt开发软件也积累了不少的经验。
  下面就是首次使用 Qt Designer 开发的插件界面
Qt Designer界面开发
  在Maya打开的界面是这个样子的
Maya打开的界面样子
  其实 Qt Designer 大大减低了界面开发的难度

UI功能开发

  这次我不仅开发出了可折叠式的UI按钮界面,还开发了可以存储最后一次设置的json功能。

折叠式UI开发

  其实原理很简单,我当时在制作PLB下载器的时候就有做过类似的效果。
  原理就是点击按钮的时候将QWidget的setvisibled函数设置为False
  如此一来按钮折叠式的内容就会消失。
  然而因为消失的区域,会导致内容空缺,会导致按钮变得非常松散。
Maya松散UI的问题
  因此我想到的解决方案就是,每一次点击按钮都重置界面大小,如此一来就可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Interface(base_class,form_class):
def __init__(self):

"""
初始化相关的代码
"""

self.NameSapce_Toggle_Check = True
self.NameSpace_Toggle.clicked.connect(self.NameSapce_Toggle_Fun)

def NameSapce_Toggle_Fun(self):
if self.NameSapce_Toggle_Check:
self.NameSpace_Btn.setVisible(False)
self.NameSapce_Toggle_Check = False
self.NameSpace_Toggle.setText(u"■命名空间 - 修正")
else:
self.NameSpace_Btn.setVisible(True)
self.NameSapce_Toggle_Check = True
self.NameSpace_Toggle.setText(u"▼命名空间 - 修正")
self.adjustSize()

json存储记录功能

  我的出发点就是能够让插件记录上一次设置的选项。
  于是我参考了 Python For Maya Artist Friendly Programming 教程的写法
  每一次关闭窗口时候都会触发json数据的存储记录
  而每一次打开窗口的时候都会读取相关的json,如果没有json数据就跳过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Interface(base_class,form_class):
def __init__(self):

"""
初始化相关的代码
"""
# 判断当前路径是否存在json数据可以读取
if os.path.exists(GUI_state_PATH):
GUI_state = {}
with open(GUI_state_PATH,'r') as f:
GUI_state = json.load(f)

# 按照存储的json数据设置插件
self.Verbose_CheckBox.setChecked(GUI_state['Verbose_CheckBox'])
self.NM__CheckBox.setChecked(GUI_state['NM__CheckBox'])
self.Render_CheckBox.setChecked(GUI_state['Render_CheckBox'])
self.Namespace_CheckBox.setChecked(GUI_state['Namespace_CheckBox'])
self.UV_CheckBox.setChecked(GUI_state['UV_CheckBox'])
self.Color_CheckBox.setChecked(GUI_state['Color_CheckBox'])
self.Render_CheckBox.setChecked(GUI_state['Render_CheckBox'])
self.WS__CheckBox.setChecked(GUI_state['WS__CheckBox'])
self.Vis__CheckBox.setChecked(GUI_state['Vis__CheckBox'])
self.NameSapce_Toggle_Check = GUI_state['NameSapce_Toggle_Check']
self.Export_Toggle_Check = GUI_state['Export_Toggle_Check']
self.Convert_Toggle_Check = GUI_state['Convert_Toggle_Check']
# 切换到正确的按钮方式
self.NameSapce_Toggle_Fun()
self.Export_Toggle_Fun()
self.Convert_Toggle_Fun()
self.NameSapce_Toggle_Fun()
self.Export_Toggle_Fun()
self.Convert_Toggle_Fun()

# 关闭窗口时保存当前视窗选择
def closeEvent(self, event):
# 存储当前界面选择
GUI_state = {}
GUI_state['Verbose_CheckBox'] = self.Verbose_CheckBox.isChecked()
GUI_state['NM__CheckBox'] = self.NM__CheckBox.isChecked()
GUI_state['Render_CheckBox'] = self.Render_CheckBox.isChecked()
GUI_state['Namespace_CheckBox'] = self.Namespace_CheckBox.isChecked()
GUI_state['UV_CheckBox'] = self.UV_CheckBox.isChecked()
GUI_state['Color_CheckBox'] = self.Color_CheckBox.isChecked()
GUI_state['WS__CheckBox'] = self.WS__CheckBox.isChecked()
GUI_state['Vis__CheckBox'] = self.Vis__CheckBox.isChecked()
GUI_state['NameSapce_Toggle_Check'] = self.NameSapce_Toggle_Check
GUI_state['Export_Toggle_Check'] = self.Export_Toggle_Check
GUI_state['Convert_Toggle_Check'] = self.Convert_Toggle_Check

with open(GUI_state_PATH,'w') as f:
json.dump(GUI_state,f,indent=4)

主功能开发

按面给材质

  正如推文所说的,Houdini的Alembic需要保留Maya的面组信息来记录材质。
  只要面组信息是对的,场景中有相关的材质,导入Alembic的时候,Maya会自动连接起来。
  问题在于,如果某些材质是对物体进行着色的话,那么导入Maya是没有面组信息的。
  因此解决方案就是将物体着色转为对物体的每个面进行着色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def Convert_Fun(self):
# 获取当前选择
sel = cmds.ls(sl=True,fl=True)

# 循环当前选择
for obj in sel:

# 获取SG节点
shapeNode = cmds.listRelatives(obj,children=True,shapes=True)

SGNodeList = cmds.listConnections(shapeNode[0],type="shadingEngine")

SGNodeList = list(set(SGNodeList))

# 循环SG节点
for SGNode in SGNodeList:
# 通过SG节点获取材质球
shader = cmds.listConnections(SGNode + ".surfaceShader")

cmds.select(cl=True)

# 选择材质球赋予的物体
cmds.hyperShade( objects=shader[0] )

# 将当前选择转换为面
cmds.ConvertSelectionToFaces()

faceList = cmds.ls(sl=True,fl=True)

cmds.sets(cl=(shader[0]+"SG"))

# 循环材质上的面重新赋予材质
for face in faceList :
if obj == face.split('.')[0]:
cmds.select(face)
cmds.sets(add=(shader[0]+"SG"))

mel.eval("maintainActiveChangeSelectMode " + sel[-1] + ";")
cmds.select(cl=True)
cmds.headsUpMessage( u'转换成功' )

模型材质传递

  这个功能是给现有的完全相同的来个物体传递两者的材质
  这个写法其实参照公司之前写好的脚本修改的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def Transfer_Fun(self):
sel = cmds.ls(sl=True,fl=True)

shapeNode = cmds.listRelatives(sel[0],children=True,shapes=True)

SGNodeList = cmds.listConnections(shapeNode[0],type="shadingEngine")

SGNodeList = list(set(SGNodeList))

for each in SGNodeList:
cmds.hyperShade(objects=each)
sel_mat_face=cmds.ls(sl=True)

##剔除不是本物体的面 (按材质组选的面,有可能选择其他物体)
mat_face_use=[]
for each_face in sel_mat_face:
if each_face.find(sel[0])!=-1: ##没有找到就返回-1
print each_face
mat_face_use.append(each_face)
print mat_face_use
##改为目标物体的面
mat_face_obj=[]
for each_new in mat_face_use:
mat_face_obj.append( each_new.replace(sel[0],sel[1]) )

cmds.select( mat_face_obj , r=True )
cmds.hyperShade( assign = each )

cmds.select(cl=True)
cmds.headsUpMessage( u'传递成功' )

Alembic 勾选相关参数导出

  要设置选项中的参数其实我是完全没有头绪的。
  毕竟这个东西的勾选是没有任何命令回显的,所以最初完全没有头绪。
  但是想起ADV5绑定的时候确实是可以设置相关的数据的。
  于是我就参考了ADV5绑定设置中的源码,找到了蛛丝马迹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def Alembic_Export_Fun(self):

check = self.Check_CheckBox(self.UV_CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportUVWrite',check))

check = self.Check_CheckBox(self.Face_CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportWriteFaceSets',check))

check = self.Check_CheckBox(self.Verbose_CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportVerbose',check))

check = self.Check_CheckBox(self.Color_CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportWriteColorSets',check))

check = self.Check_CheckBox(self.Render_CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportRenderableOnly',check))

check = self.Check_CheckBox(self.WS__CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportWorldSpace',check))

check = self.Check_CheckBox(self.NM__CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportNoNormals',check))

check = self.Check_CheckBox(self.Vis__CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportWriteVisibility',check))

check = self.Check_CheckBox(self.Namespace_CheckBox.isChecked())
cmds.optionVar(iv=('Alembic_exportStripNamespaces',check))

# 触发 Alembic 导出
cmds.AlembicExportSelection()

  使用 cmds.optionVar(l=True) 可以查看optionVar相关的属性变量

install.mel 的搭建

  鉴于对ADV5的安装方法的好奇,我也想开发出这种方便的安装方式。
  本来我是想尝试直接将开发好的py文件扔进Maya,看看能不能如我所愿地执行插件。
  经过测试,Maya2017是完全没问题的,但是Maya2015就无法这样直接执行了。
  所以参考了ADV5的方法,我发现它是通过Mel来实现的
  于是,对ADV5的install.mel进行魔改,成功实现了好ADV5差不多的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
string $subDir = "abc_helper/";
string $scriptName="main";

string $scirptExt = "py";
string $ImagePath = "/icon/alembic_logo" ;

global string $gShelfTopLevel;
string $currentShelf = `tabLayout -query -selectTab $gShelfTopLevel`;
setParent $currentShelf;
string $asInstallScriptLocation=`asInstallScriptLocation`;

string $command="import sys\nsys.path.append(\"" + $asInstallScriptLocation + $subDir + "\")\nimport " + $scriptName + "\nreload(" + $scriptName + ") ";
string $sourceFile=$asInstallScriptLocation+ $subDir + $scriptName+"."+$scirptExt;
string $iconExt="jpg";
if (`asMayaVersionAsFloat`<2012)
$iconExt="xpm";
string $icon=$asInstallScriptLocation + $subDir + $ImagePath + "."+$iconExt;
if (!`file -q -ex $sourceFile`)
error ("Something went wrong, can not find: \""+$sourceFile+"\"");
shelfButton
-command $command
-annotation $scriptName
-label $scriptName
-image $icon
-image1 $icon
-sourceType "python"
;

global proc asInstallScriptLocator (){}

global proc string asInstallScriptLocation ()
{
string $whatIs=`whatIs asInstallScriptLocator`;
string $fullPath=`substring $whatIs 25 999`;
string $buffer[];
string $slash="/";
if (`gmatch $whatIs "*\\\\*"`)//sourced from ScriptEditor
$slash="\\";
int $numTok=`tokenize $fullPath $slash $buffer`;
int $numLetters=size($fullPath);
int $numLettersLastFolder=size($buffer[$numTok-1]);
string $scriptLocation=`substring $fullPath 1 ($numLetters-$numLettersLastFolder)`;
return $scriptLocation;
}

global proc float asMayaVersionAsFloat ()
{
float $version=2012;
if (`exists getApplicationVersionAsFloat`)
return `getApplicationVersionAsFloat`;
string $versionString=`about -v`;
string $tempString[];
string $char;
tokenize $versionString $tempString;
//default to 2012, if versionString is not all numbers
for ($i=0;$i<size($tempString[0]);$i++)
{
$char=`substring $tempString[0] ($i+1) ($i+1)`;
if (!`gmatch $char "[0-9]"`)
return 2012;
}
$version=$tempString[0];
return $version;
}

总结

  以上就是 Alembic导入导出助手 的核心源码了。
  原理其实一点都不复杂,整个制作过程中最困难的是想出按面给材质的方法以及最后怎么实现设置导出选项。
  这个插件开发奠定自己的插件开发风格,为后面的藤蔓生长工具的开发奠定了坚实的基础。


2018-11-30 更新

  借助UI2CG工具更新了基础界面和功能
  这次删除了命名空间修改的功能,因为我发现Maya自带命名空间编辑器,比我自己的开发的小功能强多了。
  这次当然也加入了UI2CG所带来的所有特性。
UI2CG 重新开发