前言
Abc助手其实最开始是师兄跟我提的。 我记得刚到公司的第一天,师兄就让我去研究如何实现批量将材质信息上到每个面上。 这就为这个插件的诞生慢下了伏笔。
其实就我自己而言,我当时并不太理解师兄想要实现的效果。 不过就是这么凑巧的,我们的三维动画实训也遇到了Alembic导出到Houdini的材质问题。 当时咨询了师兄之后才知道,原来问题都是一样的。 而这些信息从模型信息写为模型的面信息就是出于这个考虑。
师兄提供了一篇很好的推文 ,上面详细地说明了这个导出的解决方案。 我就是基于这篇推文的思路进行开发的。
界面开发
这次是首次尝试使用 Qt Designer 来开完整的插件,不过之前也用PyQt开发软件也积累了不少的经验。 下面就是首次使用 Qt Designer 开发的插件界面 在Maya打开的界面是这个样子的 其实 Qt Designer 大大减低了界面开发的难度
UI功能开发
这次我不仅开发出了可折叠式的UI按钮界面,还开发了可以存储最后一次设置的json功能。
折叠式UI开发
其实原理很简单,我当时在制作PLB下载器的时候就有做过类似的效果。 原理就是点击按钮的时候将QWidget的setvisibled函数设置为False 如此一来按钮折叠式的内容就会消失。 然而因为消失的区域,会导致内容空缺,会导致按钮变得非常松散。 因此我想到的解决方案就是,每一次点击按钮都重置界面大小,如此一来就可以解决这个问题。
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 ): """ 初始化相关的代码 """ if os.path.exists(GUI_state_PATH): GUI_state = {} with open (GUI_state_PATH,'r' ) as f: GUI_state = json.load(f) 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: shapeNode = cmds.listRelatives(obj,children=True ,shapes=True ) SGNodeList = cmds.listConnections(shapeNode[0 ],type ="shadingEngine" ) SGNodeList = list (set (SGNodeList)) for SGNode in SGNodeList: 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 : 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)) 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 "*\\\\*"` ) $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;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所带来的所有特性。