第一次在知乎发文,技术朋友们请多包涵。
Substance Designer自去年夏天2018版推出Python API至今已有一年,从一开始的寒酸到现在的初成规模,官方的API库也随着版本的更新不断完善。目前网上(包括youtube)相关的教程甚少,即便是官方去年推出的唯一一门入门教程,也随着2019的更新而变得不再适用于当前最新的SD版本。今天就乘着兴致发一篇带案例的入门SD Python API小教程以及我自己总结的探索SD Python API的一些基本思路。
既然是Python API的相关,我这边就不过度阐述python的基本语法了。
写脚本的一开始,首先必须引入的sd的总类“sd”:
另外,SD2019已经引入了PyQt这个强大的Python GUI,所以,同时引入PySide2:
同时,由于本篇涉及到保存的操作,所以引入os模块方便提取文件路径。
SD脚本绝大部分的操作是从获取SDApplication开始的:
Python中一切皆对象,我们大部分获取的object都可以通过官方API帮助文档来获取自身相应的实例方法,这也是自我学习最主要途径之一,一定要多翻官方API帮助文档:Help -> Python API Documentation官方API离线帮助文档
回到正题,一开始我们通过sd.getContext().getSDApplication()获得了SDApplication对象并赋予到了自己定义的app这个变量上。
接下来我们要得到3个Manager:sdPackageManager、uiManager、qtUIManager。
这三个manager都是从app中得到,去到sdapplication module帮助文档,可以查阅到获取方法:获得PackageMgr获得QtForPythonUIMgr获得UIMgr
这里值得一提的是这个QtForPythonUIMgr(),帮助文档里并没有写返回的东西,只有三个点... 它的信息可以再qtforpythonuimgrwrapper里找到:
进去之后。找到getMainWindow():
并用一个变量接受返回的QMainWindow:
至此,写ui类之前的准备工作基本完成。因为要写保存功能,所以预先存储一个地址的变量saving_path在外面。当然这变量也能写在类的里面,因为用户可能要在UI上自己指定地址。
进入到类的编写:
在类的构造函数中,这个类继承自QtWidgets.QDialog,使用super先执行QDialog的构造函数,并将parent变量先继承下来,同时加上自己定义的实例变量pkg_mgr和abs_saving_path。
UI窗口的名字直接使用setWindowTitle('..')来定义:一开始的UI窗口名字
这边的custom_print _field先不用管。这是下面要用的输入栏的实例变量。
在构造函数最后运行build_ui()实例方法来创建UI。build_ui方法会在下面定义:
要用文字一行行解释过于麻烦,我总结一些我这个UI创建的大致思路:
首先得定义一个总Layout,master_layout,用来控制整体的Layout走向。QVBoxLayout则是Qt中整体纵向排布的基本布局,作用在self也就是实例上来控制整体走向。
确定整体走向之后,可以分批定义child widget,我这里有两个child widget,分别是custom_print _widget和creation_widget。
child widget定义完之后,可以设定这个child widget自己的layout(作用在自身):
先展示一下这个custom_print_widget的基本效果:
这是一个横向的布局也就是QHBoxLayout的作用。左边是一个输入框,右边是一个按钮。输入框由于需要在另外的实例方法中继续提取它的信息,所以把它当做一个实例变量定义在构造函数中了:
这个输入栏的对象是由QtWidgets.QLineEdit()获取而来。
按钮的对象由QtWidgets.QPushButton()获取而来:
点击的发生的事件则由clicked.connect(实例方法)来建立。
创建完了两个组件之后,需要把它们按顺序添加到child widget的layout中:
这样一个完整的child widget就写完了。
最后,将这个child widget添加到master_layout中:
这就是基本的QtUI模块的创建思路。creation_widget的创建思路也完全一样,就不过多做阐述了。UI创建好了之后,再在类里添加一个展示UI的实例方法:show()是QDialog可以直接调用的方法
在类的外部,建立一个实例,并调用display_window方法来显示UI:
接下来阐述一下save的实现:
这里需要用到一开始定义的sdPackageMgr,通过它可以获取工作目录下的package:
使用getUserPackages()方法(在帮助文档的sdpackage module下可以找到)获取到一个存储着sdPackage object的SD数组(SD Array),使用for循环遍历这个数组来提取每一个工作栏下的package,我们可以利用getFilePath()的返回来确认它是否有被保存:
如果通过getFilePath()可以返回一个确切路径的话,那就说明这个package已经被保存过了,如果返回的是一个空值NoneType,那就说明这个package没有被保存过。
对于已经保存过的package,可以使用pkgMgr下的savePackage()方法来进行覆盖保存工作,对于没有保存过的package,可以使用pkgMgr下的savePackageAs()方法来进行第一次的另存为保存:
savePackageAs方法后面除了sdPackage这个变量之外,还需要一个绝对路径(以.sbs后缀结尾)。输入保存名称并点击save
可以看到在相关路径下,package被正确保存下来了。
接下来解析下面四个按钮的作用和实现:
这四个按钮的功能是创建对应的SD节点,非常直观的四个常用节点名称。
这里的思路是这样的,一般情况下,四个节点的clicked事件会绑定不同的四个方法,因为clicked.connect()里面传入的是方法名而不是实现:
所以说,如果有非常多的节点,那么常规思路下就得定义对应数量的方法来判断到底用户点击了哪个按钮,但这样写的话,代码量就会变得非常庞大。我这里的思路是通过Qt里sender()的方法来直接宏观地判断用户究竟点击了哪个按钮,这样所有的按钮都可以连接到同一个方法中,而不需要像常规一样定义很多个方法:使用变量接受sender()的返回信息
这个button_clicked返回的就是QPushButton object,我们可以通过在后面加.text()方法来获取这个按钮上面的名字:
知道了返回的Button信息之后,就可以预先准备好一个字典或者switch对象来根据Button的信息直接使用创建节点的方法:
这是我在类的一开始定义的类变量。字典的key值就是Button给我的信息,我就可以通过key来获取相应的value。
关键来了,关于字典里value的值,并不是随随便便定义的,它的定义是取自节点的ID字符串。这么做的目的是配合创建节点的newNode()方法的参数条件。newNode()方法是通过uiMgr.getCurrentGraph().newNode()获得:
通过sduimgr下的getCurrentGraph()方法获得当前的SDGraph,再利用SDGraph的newNode方法来创建新节点:
newNode方法的必要参数是sdDefinitionId,看解释是通过sdDefinition这个类下的identifier来获取。sdDefinition这个类我很熟悉,它在sdnode module下有出现:
点击进入sdDefinition module,可以看到有一个getId()方法:
这就是我们想要的sdDefinitionId的字符串。
那我们如何知道这些具体节点的sdDefinitionId是啥?很简单,先手头创建这四个目标节点,然后直接调用getDefinition().getId()预先获取了就行:
这就是这四个节点的sdDefinitionId,最后的字样也就是字典里的value。
通过newNode,传入相应的sdDefinitionId数据,创建出我们需要的节点: