第一节

Maya帮助文档的查阅方法

image.png
第一个框住的是命令的名称,也可以叫他函数
第二个框住的是命令的标识与参数,只需要在其中找到自己需要的几个使用就可以了
第三个框住的是返回值
第四个框住的是与查询的命令相似的命令
image.png
Flags下面是命令的标识与参数的使用方法
最左边是每个标识的全称与简称,中间是标识所需要的参数的数量与类型,右边是标识的特性
标识的特性介绍:
image.png
介绍一下M:例如设置球体半径,因为球体半径只有一个值,因此不能多次使用,但是创建曲线的时候,曲线由许多点构成,每个点都有不同的数值,在创建时可以多次使用他点位置的标识,然后跟上不同点的坐标,然后来创建一个曲线。
命令帮助文档的最下方是官方的帮助案例
image.png

如何在maya中使用python

maya的命令在python中是以包的模式出现的。要使用maya的命令必须要先导入模块才能进行调用。
MEL命令与python命令在参数或者标识上调用的区别:
image.png

第二节

获取场景中的操作对象

在编程中,在确定对一个物体进行操作之前需要通过命令找到要操作的对象。
image.png

  1. 通过对象的名字获取单一物体
  2. 通过类型获取一类物体
  3. 通过UUID获取某一物体(在名字出现重复的情况下)

获取对象的命令:ls
mc.ls()返回的结果是场景中所有的节点名称的列表
mc.ls(sl=True)返回的结果是在窗口的选择的对象的名称 sl是selection的简称
mc.ls(typ=(‘mesh’,‘nurbsCurve’)) 作用是列举场景中的所有多边形和曲线两种类型的物体,typ是tpye的简称,因为type是python的关键字所以使用在maya中使用typ
ls命令列举的名字都是短名(物体本身的名字),为了防止出现重名的情况,通过long=True列举它们的长名
mc.ls(typ=(‘mesh’,‘nurbsCurve’),long=True)

如果想要列出指定类型的所有对象,但不列出该类型的后代对象。例如想要列举transform类型的对象(模型曲线等)但不想要列举由transform扩展出来的对象(例如骨骼)就可以通过exactType(et)
mc.ls(et=‘transform’)
mc.ls(ext=‘transform’) # excludeType(ext) 列举除了transform类型外的所有物体
根据名字过滤物体:
mc.ls(“cn”) # *号代表多个字母
mc.ls(“blendShape??Set”) # 一个?号代表一个字符
根据UUID选择物体:
UUID查询方法:
image.png"
mc.(“UUID的名字”)
ls命令参数:
image.png
image.png
image.png

rename命令

image.png
批量重命名:

1
2
3
4
import maya.cmds as mc
for shape in mc.ls(typ='mesh'):
mc.rename(shape,'mesh_{0}'.format(shape))

image.png

第三节获取与改变场景中的层级关系

image.png
通过mc.listRelatives()命令可以处理层级关系
mc.listRelatives(‘Mery_grp’,p=True) # 得到Mery_grp的父物体名字(只有一个)
mc.listRelatives(‘Mery_grp’,c=True) # 得到Mery_grp的子物体名字(只得到“儿子”,没有“孙子”)
mc.listRelatives(‘Mery_grp’,ad=True) # ad是allDescendents的简称,得到Mery_grp的所有子物体名字
mc.listRelatives(‘Mery_grp’,ad=True,typ=‘joint’) # 得到Mery_grp的所有子物体名字(只要joint类型的)
mc.listRelatives(‘Mery_grp’,ad=True,typ=‘joint’,f=True)# f是fullPath的简称,防止子物体有重名的情况,列举长名
image.png

修改物体的层级关系

parent命令:
mc.parent(‘pSphere1’,‘Mery_grp’) # 讲pSphere1放到Mery_grp的下面

1
2
3
import maya.cmds as mc
for obj in mc.ls(sl=True) :
mc.parent(obj,'Mery_grp')

mc.parent(‘pSphere1’,w=True) # w为world的缩写,这个命令的作用是将pSphere1放到世界层级的下边,这样pSphere1就没有任何父物体了。
image.png
group命令:
mc.group(mc.ls(sl=True),name=‘ball_group’) # 将选择的物体进行打组并起组的名字叫ball_group,第一个参数接收的是一个列表因为mc.ls(sl=True)返回的就是一个列表,因此不需要再增加中括号了。
image.png

第四节

获取与更改物体的属性

move,rotate,scale命令,移动,旋转,缩放

命令 解释
mc.move(0,1,0,‘pCube1’,r=True) 将pCube1在y轴方向移动1个单位,r是relative的缩写,意思是自身坐标
mc.move(0,1,0,‘pCube1’,a=True) 将pCube1移动到世界坐标(0,1,0)的位置,a是absolute的缩写,意思是世界坐标
mc.rotate(0,10,0,‘pCube1’,r=True) 将pCube1在y轴方向旋转10度,r是relative的缩写,意思是自身坐标
mc.rotate(0,1,0,‘pCube1’,a=True) 将pCube1y轴的旋转属性值设置为10度,a是absolute的缩写,意思是世界坐标
mc.scale(1,5,1,‘pCube1’,r=True) 将pCube1在y轴方向放大5倍
mc.scale(1,5,1,‘pCube1’,a=True) 将pCube1的缩放属性设置为1,5,1

xform命令(不能使用缩放),可以做绑定上的无缝切换

命令 解释
_t = mc.xform(‘locator1’,q=True,t=True) 查询locator1的移动属性(transform)并将结果传入给变量_t (列表),q是查询的意思
mc.xform(‘locator2’,t=_t ) 将刚才定义的_t变量 的数值传递给locator2的移动属性,默认是以自身坐标空间改变
_ro = mc.xform(‘locator1’,q=True,ro=True) 查询locator1的旋转属性(rotation)并将结果传入给变量_ro(列表)
mc.xform(‘locator2’,ro=_ro ) 将刚才定义的_t变量 的数值传递给locator2的旋转属性,默认是以自身坐标空间改变
tt = mc.xform(‘locator1’,q=True,t=True,ws=True) ws为True意思是以世界坐标空间获取
mc.xform(‘pSphere1’,t=tt,ws=True) 将locator1的世界坐标信息给pSphere1。

image.png

第五节常见节点、物体的创建

创建某一确定物体的命令

创建球体:

1
2
import maya.cmds as mc
mc.polySphere(r=2,sx=5,sy=5) # 球体的半径为2,x,y的细分(subdivisions)都设置为5,

创建圆环曲线:

1
2
import maya.cmds as mc
mc.circle(r=10,s=20,nr=(0,1,0)) # 圆环的半径为10,段数(sections)设置为20,法线为(0,1,0)

创建曲线:

1
2
import maya.cmds as mc
mc.curve(d=1,p=[[0,0,0],[0,1,0],[0,1,1]]) # d(degree)的意思是曲率,p的意思是点

创建骨骼:

1
2
3
import maya.cmds as mc
for i in range(10) :
mc.joint(p=(0,i,0)) # 使用此命令创建骨骼时,新创建的骨骼默认设置为当前选择的对象的子物体

image.png

万能创建法 createNode

这个方法是创建节点使用的。可以创建的节点如下:
https://help.autodesk.com/view/MAYAUL/2019/ENU/index.html?contextId=NODES-INDEX

1
2
3
import maya.cmds as mc
for i in range(10) :
mc.createNode('joint') # 创建的骨骼都是在同一层级下,createNode只是单纯创造节点使用的

image.png

第六节获取节点的类型和属性

获取选择物体的类型nodeType

import maya.cmds as mc
sel = mc.ls(sl=True)[0]
print mc.nodeType(sel)

获取物体所有属性listAttr

import maya.cmds as mc
print mc.listAttr(‘pCube1’)

获取物体可k帧属性

import maya.cmds as mc
print mc.listAttr(‘pCube1’,k=1)

获取物体自定义属性(ud)

import maya.cmds as mc
box = ‘pCube1’
for attr in mc.listAttr(box, ud=True):
print ‘{0}.{1}’.format(box, attr)

查询属性getAttr(参数是名称加上点加上属性名)

import maya.cmds as mc
print mc.getAttr(‘pCube1.sy’)

修改属性setAttr

import maya.cmds as mc
print mc.setAttr(‘pCube1.sy’,1)
如果属性是字符串类型的(用户自定义的属性)image.png
那么需要属性值是带引号的同时也需要在后面加一个typ=‘string’
举例
mc.setAttr(‘pCube1.str’,‘1’,typ=‘string’)
mc.setAttr(‘pCube1.str’,‘1’,typ=‘string’,l=True) # 赋值后并将属性锁定

第七节连接属性、断开属性、属性联动

常见的连接属性的方法

image.png
左边的属性影响右边的属性。

连接属性 connectAttr命令

mc.connectAttr(‘pCube1.t’,‘pSphere1.t’) # 将pCube1的translate属性与pSphere1的translate属性连接起来
mc.connectAttr(‘pCube2.t’,‘pSphere1.t’) # 会提示错误,因为pSphere1已经处于连接状态
mc.connectAttr(‘pCube2.t’,‘pSphere1.t’.f=True) # f是force的简写,代表强制连接pCube2与pSphere1。

断开属性 disconnectAttr命令

mc.disconnectAttr(‘pCube2.t’,‘pSphere1.t’)

第八节获取节点的连接关系

命令:listConnections 获取节点的连接信息

可用参数:
source 简称s 上游,来源
destination简称d 下游,目标
plugs 简称p 接口,返回连接的节点的属性的名字,参数为布尔类型(如果想要获取属性名字也可以通过connectionInfo命令获取)
type 简称t 根据类型返回节点信息
**举例:**mc.listConnections(‘blendColors1’,s=True,d=False,p=True,t=‘lambert’)

命令:connectionInfo 获取节点属性的连接信息

可用参数:
sourceFromDestination简称sfd,为True时返回上游属性(返回的是一个unicode)
destinationFromSource简称dfs,为True时返回下游属性(返回的是一个列表)
isSource不能用简称,因为is是关键词,为True时判断目标属性是否为上游属性(是否是用来输出的源)
isDestination不能用简称,因为id是关键词,为True时判断目标属性是否为下游属性
举例:
mc.connectionInfo(‘blendColors1.output’,dfs=True)

第九节 约束

常见的约束:image.png从上到下依此为,父子约束、点约束、旋转约束、缩放约束、目标约束、极向量约束。
这里介绍一下点约束、旋转约束、父子约束。

点约束:pointConstraint

能使用命令调用的属性都包含在这里面image.png
平时的约束操作:选择约束的物体然后再选择被约束的物体然后执行约束。
使用命令进行约束:mc.pointConstraint(‘pCube1’,‘pSphere1’)第一个为约束的物体,第二个为被约束的物体
保持偏移的情况下进行约束:mc.pointConstraint(‘pCube1’,‘pSphere1’,mo=True) mo为maintaniOffset的缩写
约束的同时赋予约束节点名字: mc.pointConstraint(‘pCube1’,‘pSphere1’,mo=True,n=‘ball_cons’)
**约束信息的查询 **
查询一个被约束物体的约束节点名字:mc.pointConstraint(‘pSphere1’,q=True,n=True)
查询一个被约束物体的约束物体名字(列表):mc.pointConstraint(‘pSphere1’,q=True,tl=True) tl为targetList的简称
查询一个被约束物体的约束节点权重别名列表:mc.pointConstraint(‘pSphere1’,q=True,wal=True) wal为weightAliasList的简称

旋转约束orientConstraint

跟点约束是一样的,就是把point改成orient 其他就都一样了

父子约束parentConstraint

实例:利用控制器对骨骼进行约束
在实际运用中控制器(可以用曲线当控制器)的轴向要和骨骼的轴向一致。
因此可以先为控制器创建一个组,然后通过父子约束使骨骼约束控制器的组,这样控制器作为组的子物体就与骨骼轴向一致了,再将这个约束删除掉,再执行父子约束使控制器约束骨骼。就可以实现使控制器与骨骼轴向并且绑定到了一起。
代码实现:

1
2
3
4
5
6
import maya.cmds as mc
jt1='joint1'
ctg='ctl_grp'
ctl='ctl'
mc.delete(mc.parentConstraint(jt1,ctg))
mc.parentConstraint(ctl,jt1)

第十节 关键帧

设置关键帧setKeyframe

命令 执行效果
mc.setKeyframe() 为选择物体所有属性k帧
mc.setKeyframe(‘pSphere1’) 为pSphere1所有属性k帧
mc.setKeyframe(‘pSphere1’,at=‘tx’) 为pSphere1的tx属性k帧
mc.setKeyframe(‘pSphere1’,at=[‘tx’,‘ty’]) 为pSphere1的tx,ty属性k帧
mc.setKeyframe(‘pSphere1’,at=[‘tx’,‘ty’,‘r’]) 为pSphere1的tx,ty,rx,ry,rz属性k帧
mc.setKeyframe(‘pSphere1’,at=[‘tx’,‘ty’,‘r’],v=10) 为pSphere1的tx,ty,rx,ry,rz属性k帧,并指定属性的值为10
mc.setKeyframe(‘pSphere1’,at=[‘tx’,‘ty’,‘r’],v=10,t=10) 为pSphere1的tx,ty,rx,ry,rz属性在第10帧k帧,并指定属性的值为10

可以通过此命令进行的实际案例举例:

  1. 不通过Maya的导出动画工具,我们可以自定义导出类型,然后读进来,用这个命令给控制器赋值上动画,就可以做一个导出导入动画的工具了。
  2. 让一个物体按照数学曲线来实现他的运动,例如让一个球沿着原点旋转,可以让这个球的X轴为正弦,Z轴为余弦的方式配合着来实现让球转圈的运动。
1
2
3
4
5
6
7
import maya.cmds as mc
import math
for i in range(0,360) :
x=math.sin(i*math.pi/180)*10 # 在程序中的数学计算默认是以弧度为单位进行运算的,因此将角度转换为弧度
z=math.cos(i*math.pi/180)*10
mc.setKeyframe('pSphere1',at='tx',t=i,v=x)
mc.setKeyframe('pSphere1',at='tz',t=i,v=z)

获取与编辑关键帧keyframe

命令 执行效果
mc.keyframe(‘pSphere1’,q=True,tc=True) tc为timeChange的缩写,返回的是一个列表,记录着物体所有存在关键帧的时间
mc.keyframe(‘pSphere1’,q=True,vc=True) vc为valueChange的缩写,返回的是一个列表,记录着物体所有存在关键帧的数值
mc.keyframe(‘pSphere1’,q=True,t=(101,101),at=“tx”,eval=True) 得到pSphere1在101帧时tx属性的值

将物体的某一范围关键帧整体移动(编辑关键帧)

1
2
3
4
import maya.cmds as mc
frame_tc=mc.keyframe('pSphere1',q=True,tc=True)
mc.keyframe('pSphere1',e=True,time=(min(frame_tc),max(frame_tc)),tc=5,r=True)
# 解释一下这些参数的意思,e代表开启编辑模式,time代表帧数的范围,tc代表移动的值(正为右,负为左),r代表相对模式

执行前:image.png
执行后:image.png

第十一节文件IO操作

命令 执行结果
**file命令 ** 处理文件与文件路径相关的
mc.file(new=True) 新建一个场景,如果没保存会无法执行
mc.file(new=True,force=True) 强制新建一个场景,没有保存的数据会清空且操作无法撤回。
mc.file(rename=‘D:/ball.ma’)
mc.file(save=True,typ=‘mayaAscii’)
这两行代码代表的意思为,第一行执行ctrl+s中的选择位置并命名指定格式的操作,第二行执行保存的操作(默认类型为mb格式,如果保存的类型为ma格式需要更改typ的参数为mayaAscii)
mc.file(save=True,typ=‘mayaAscii’) 对于已经创建好的场景可以直接执行保存的命令
mc.file(‘D:/ball.ma’,o=True,f=True) 强制打开对应的路径场景(需要带后缀名)
mc.file(q=True,sn=True) 查询场景的路径文件名(带路径名),sn是sceneName的缩写。
mc.file(q=True,sn=True,shn=True) 查询场景的文件名(不带路径名),shn是shortName的缩写
mc.file(‘D:/ball_export.ma’,ea=True,typ=‘mayaAscii’) 导出所有物体并指定路径文件名,ea是exportAll的缩写
mc.file(‘D:/ball_export.ma’,es=True,typ=‘mayaAscii’) 导出选中的物体并指定路径文件,es为exportSelected的缩写
mc.file(‘D:/ball_export.ma’,i=True,ns=‘ball’) 进行导入文件操作并提供命名空间为ball,加命名空间的名字是为了区分物体,i是import的缩写使用简称,因为import是python的关键字,ns是namespace 的缩写image.png
mc.file(‘D:/ball_export.ma’,r=True,ns=‘ball’) 进行引用文件操作并提供命名空间为ball,r是reference的缩写。
mc.file(q=True,r=True) 查看当前场景引用的文件路径,返回的是一个列表
mc.file(‘D:/ball_export.ma’,r=True,ns=‘ball’) 查看当前场景中引用的D:/ball_export.ma文件中的引用文件路径,针对嵌套引用进行的操作
**referenceQuery命令 ** 查询引用文件的信息
mc.referenceQuery(‘D:/ball_export.ma’,n=True) 查询引用文件的所有节点信息(列表)
mc.referenceQuery(‘ball:pSphere50’,f=True) 查询当前场景中的文件的文件路径,f为filename的缩写。
mc.referenceQuery(‘D:/ball_export.ma{3}’,isLoaded=True) 查询当前场景中引用的第四个文件有没有加载进场景中。

第十二节maya界面编程

image.png

命令 执行结果
wnd=mc.window()
mc.showWindow(wnd)
创建一个简单的窗口
显示窗口
wnd=mc.window(w=600,h=800,t=‘My Window’) 创建一个宽600高800标题为My Window的窗口
1
2
3
4
5
6
7
8
9
10
11
12
import maya.cmds as mc
wnd_name='my_window' # 为窗口设置一个名字
if mc.window(wnd_name,q=True,ex=True):
# 如果界面中出现已经打开的窗口,就先删掉窗口,再执行下面的语句(创建窗口)
mc.deleteUI(wnd_name,wnd=True)
if mc.windowPref(wnd_name,q=True,ex=True):
# Pref是preference(偏好)的简称,如果创建窗口后更改了窗口的大小,就会移除这个偏好设置,r为remove的简称。
# 这个操作可以理解为初始化
mc.windowPref(wnd_name,r=True)
# 生成一个窗口名称为wnd_name标题为My Window的窗口,用wnd来存取
wnd = mc.window(wnd_name,w=400,h=300,t='My Window')
mc.showWindow(wnd)
1
2
3
4
5
6
7
8
9
10
11
import maya.cmds as mc
wnd_name='my_window'
if mc.window(wnd_name,q=True,ex=True):
mc.deleteUI(wnd_name,wnd=True)
if mc.windowPref(wnd_name,q=True,ex=True):
mc.windowPref(wnd_name,r=True)
wnd = mc.window(wnd_name,w=400,h=300,t='My Window')
mc.columnLayout() # 垂直布局
for i in range(10):
mc.button(l='Button_{0}'.format(i),c='print {0}'.format(i)) # 按钮以及对应事件
mc.showWindow(wnd)

可以在帮助文档中的这一栏找到生成界面的操作命令image.png

第十三节maya界面视图操作

命令 执行结果
mc.getPanel命令 获取面板
mc.getPanel(all=True) 获取maya中的所有面板名字列表,all为(allPanels的缩写)
mc.getPanel(vis=True) 获取显示的面板名字列表(一般会包括脚本编辑面板和大纲面板),vis为(visiblePanels的缩写)image.png
mc.getPanel(typ=‘modelPanel’) 获取类型为modelPanel的面板,[u’modelPanel1’, u’modelPanel2’, u’modelPanel3’, u’modelPanel4’]
image.png无论对应不同面板对应视图是什****么,面板名都不会改变
mc.getPanel(withFocus=True) 获取当前激活的面板名字。(实时获取需要将代码变成工具后才能实时获取)
mc.modePanel命令 编辑面板
mc.modelPanel(‘modelPanel4’,q=True,cam=True) 查询modelPanel4面板的相机名字
mc.modelPanel(‘modelPanel4’,q=True,p=True) 查询modelPanel4面板的父物体的名字
mc.modelPanel(‘modelPanel4’,e=True,cam=‘camera1’) 编辑modelPanel4面板,使其相机设置为camera1
mc.modelPanel(‘modelPanel4’,e=True,tearOff=True) 将modelPanel4面板裁剪下来,image.png
要想恢复默认面板点击这个image.png
mc.modelEditor命令 针对视图面板进行编辑操作
mc.modelEditor(‘modelPanel4’,q=True,cam=True) 获取modelPanel4面板中的摄像机名字
mc.modelEditor(‘modelPanel4’,e=True,j=False) 编辑modelPanle4面板,取消骨骼的显示
mc.modelEditor(‘modelPanel4’,e=True,pm=False) 编辑modelPanle4面板,取消多边形的显示
mc.modelEditor(‘modelPanel4’,e=True,xray=True) 编辑modelPanle4面板,是物体以x光的样式显示image.png
mc.modelEditor(‘modelPanel4’,e=True,grid=False) 编辑modelPanle4面板,取消网格的显示

第十四节 Maya ScriptJob 事件操作

时间变化事件

mc.scriptJob(tc=‘print “xxx”’) 意思是当时间轴变化的时候会输出“xxx”
每通过scriptJob创造一次事件后,会生成对应的ID来代表事件,如果重复使用scriptJob事件变化时,会产生多次事件操作,因此每创造一次事件操作后需要通过mc.scriptJob(kill=对应事件的ID)来删除掉。

属性变化事件

mc.scriptJob(attributeChange=(‘pSphere1.tx’,‘print “123”’))意思是当pSphere1的x坐标发生改变时就输出123
mc.scriptJob(killAll=True) 意思是删除所有变化事件

节点变化事件

mc.scriptJob(nodeDeleted=(‘pSphere1’,‘print “ball delete”’)) 当pSphere1节点被删除时输出ball delete
mc.scriptJob(nodeNameChanged=(‘pSphere1’,‘print “ball renamed”’)) 当pSphere1节点名字改变时,输出ball renamed,**无论名字怎么变都能追踪到节点,但是无法追踪的更改后的名字。**如果需要追踪的更改后的名字需要用到api的知识。
mc.scriptJob(nodeNameChanged=(‘pSphere1’,‘print “ball renamed”’,permanent=True)) permanent是永久的意思,为True时代表这个事件不会被kill掉,只有关闭maya时才会kill掉。

根据条件是否成立进行的事件

mc.scriptJob(conditionTrue=(‘autoKeyframeState’,‘print “auto key enable”’) )如果开启自动关键帧,则输出auto key enable
mc.scriptJob(conditionFalse=(‘autoKeyframeState’,‘print “auto key disable”’) )如果关闭自动关键帧,则输出

mc.scriptJob(conditionFalse=(‘SomethingSelected’,func) )如果选择了一个物体,则调用func函数(func函数是自己定义的)
mc.scriptJob(listConditions=True) 列举所有可以判断的条件

第十六节Maya中的Pymel编程

https://vannyyuan.github.io/2020/10/30/maya/Maya%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%E8%AF%BE/Maya%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%E8%AF%BE%EF%BC%88%E4%BA%8C%EF%BC%89/
image.png
image.png

第十八节自定义maya环境

为什么要修改maya的环境变量

我们可以通过环境变量来配置我们不同项目中所需要的maya环境,可以通过环境变量来存储、获取信息,也可以直接修改maya的配置,可以通过设置统一的环境变量,使每个人的环境设置都是统一的。如果我们使用动态设置环境变量的话,我们可以更方便地去切换配置来制定我们的运行环境。
image.png
上面一栏是针对用户的变量,系统变量不区分用户,只要用的是同一台电脑都会读取系统变量。
用户变量会覆盖系统变量,

在进程中设置环境变量

在进程中设置的环境变量只针对当前运行的程序,在进程中设置是指,image.png
关闭程序框之后设置的环境变量就消失了。

通过设置环境变量关闭maya的用户登录节省启动时间

在搜索框中搜索env可以快速进入编辑环境变量的界面。
image.png
image.png
常用的环境变量设置:
image.png

第十九节maya api基础知识

image.png

API编译的插件后缀

Linux:.so
Windows: .mll
Mac OS X: .bundle
通用型插件: .py 直接运行

API的内置库

OpenMaya 基本的操作工具类
OpenMayaUI 界面工具类
OpenMayaAnim 动画工具类
OpenMayaFX 特效工具类
OpenMayaRender 渲染工具类

API命名规则

M classes - 基本的数据类型 比如:类似于python的字符串、整数
MFn - Function 函数工具类型
MIt - lterator 迭代器类型
MPx - 代理类型,扩展Maya功能需要继承的类

MayaAPI通过MFn把不同的物体方法归类,我们可以通过这些方法,针对不同的物体进行各自的操作
MIt开头的迭代器类型:这种类型对于我们批量访问物体也就是说对于一批物体我们要逐个访问的时候,我们要使用这种迭代器类型,然后用循环挨个访问它们的元素。
MPx类型这个类型不是由我们来访问的,是我们按照这种格式编写好了一个插件,那么加载上之后,Maya就去根据他定义好的固定的格式来去加载这些插件,MPx类型就是我们由人工来编写,Maya自己来识别的这个命令

DependencyNode和DagNode

在maya中,所有的节点都是一个DependencyNode也就是说依赖节点,我们所有的数据都是依赖于节点来计算的,每个节点存储了我们所需要的数据,那么节点之间的相互计算,形成节点网络,也就形成了我们最终的文件。
DependencyNode是最基本的节点,类似于我们的材质节点,这种独立的单个节点。
Maya DagNode是带有层级关系的,在大纲里,我们可以设置它的父子关系,这种叫DagNode,DagNode是从DependencyNode扩展而来的,DagNode拥有DependencyNode的所有方法,我们可以用DependencyNode的方法来处理任何一个DagNode,但是类似于层级操作这种,比如说获取它的物体的上下级关系这种不属于DependencyNode里面的方法,我们就要用DagNode它自有的方法,也就是我们面向对象里面的,子类的那种方法。

MObject :Maya最基本的对象指针

image.png
MObject是maya最基本的一个对象,也就是说如果使用API的话这个就是我们处理的最基本的一个数据,就像python编程一样,我们一个变量,虽然是字符串或者整数,那么它都是一个python的基本对象,这个MObject是MayaAPI的基本对象。在MayaAPI中,它不能以字符串这种来处理节点,它必须要转化成一个MObject才能处理,也就是说Maya中任何一个对象都是一个MObject,当然处理类似于它的名字、它的属性值这种字符串或者数值例外。也就是说这是一个最基本的索引,它指向 了每一个节点,我们在处理某一个节点就认为它是一个MObject

我们要处理任何一个Maya节点都要把它实例成一个MObject才能使用它的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import maya.OpenMaya as OpenMaya
import pymel.core as pm

ball_node = pm.PyNode('pSphere1') # 将”pSphere1“改为pymel节点
ball_api_node = ball_node.__apimobject__() # 利用pymel节点的方法创建一个MObject对象指向pSphere1

# 通过pymel转化的MObject已经有了对象
print ball_api_node.isNull() # False
# 通过OpenMaya直接创建的MObject是空的
print OpenMaya.MObject().isNull() # True

# 查询api类型
print ball_api_node.apiType() # 110
print ball_api_node.apiTypeStr() # kTransform
ballshape_api_node = pm.PyNode('pSphereShape1').__apimobject__()
print ballshape_api_node.apiType() # 296
print ballshape_api_node.apiTypeStr() # kMesh

# 使用 == != 判断两个物体是否相等
print ball_api_node == ballshape_api_node # False
# 使用 = 直接赋值
ballshape_api_node = ball_api_node
print ballshape_api_node.apiTypeStr()

如何查询API的帮助文档

https://help.autodesk.com/view/MAYAUL/2019/CHS/?guid=Maya_SDK_MERGED_cpp_ref_index_html
帮助文档是C++的风格,
image.png
modules是我们可以导入的模块,查询一般都是查询modules里面的内容
例如查询MObject
image.png
前三行:在最一开始我们可以通过第一行创建一个空的MObject,或者通过第二行从另一个MObject转过来它就指到这一个原来那个MObject那个节点上了。第三行带~的学名叫做析构函数,意思是被销毁时执行的操作。一般不会用到它,除非我们编写节点的时候,需要用到它来清理内存。
下面的蓝色的部分是它可以使用的方法,前面的是当前方法的返回值,括号里面是当前方法需要的参数,后面是它的描述。
静态方法:意思是不需要实例化就可以使用的方法

MDagPath

MDagPath是最基本的物体对象,我们可以把大纲里的物体通过它来交给API来处理,可以从MDagPath里面找到物体对应的MObject,也可以通过传入MObject,给物体定义好一个DagPath来处理它的层级关系

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建一个空的MDagPath
ball_dag_path = OpenMaya.MDagPath()
# 建立DagPath联系
OpenMaya.MDagPath.getAPathTo(ball_api_node,ball_dag_path)
# 获取DagPath名字
print ball_dag_path.fullPathName() # |pSphere1
print ball_dag_path.partialPathName() # pSphere1
# 判断是否显示
print ball_dag_path.isVisible() # True
# 通过MDagPath获取API类型
print ball_dag_path.apiType() # 110
# 通过MDagPath返回MObject
print ball_dag_path.node() # <maya.OpenMaya.MObject; proxy of <Swig Object of type 'MObject *' at 0x0000014894705F90>

第二十节 MFn、MIt、MPx(函数库、迭代器、代理)

MFn

image.png
MFnDependencyNode用来处理任何一个节点的普通方法
MFnDagNode用来处理大纲内的物体的一些常用方法
MFnMesh用来处理多边形的操作
MFn记录了Maya内所有的节点的类型也就是说我们用MObject获取的那个apiType返回的数值就是它在MFn列表中的索引值

MIt

image.png
MIt是为了批量处理而设计的,比如挨个处理每个层级的物体。或者比如说有一个球的任何一个节点都可以通过MItMesh来访问,然后来访问它的任何一个点线面。
MItDag:处理大纲里的层级关系
MItCurveCV:处理曲线的点
MItMeshEdge:处理多边形的线的循环
MItMeshFaceVertex: 以面点的方式处理
MItMeshPolygon:处理面
MItMeshVertex:处理顶点
MItSelectionList:处理一个列表中的任何物体(跟MItDag有区别,MItDag是处理大纲里的所有物体,MItSelectionList是处理我们自己创建的列表中的物体)

MItDependencyGraph:通过一个节点寻找它上下游所有的节点
MItDependencyNodes:可以使用它来过滤场景里面任何一个节点,Maya中的节点都可以通过它来逐个访问到

MPx

image.png
当要真正编写一个Maya中没有的物体的话,就需要继承MPx里边的内容。
我们如果要写一个命令的话,那么,它就要继承MPxCommand,如果我们要写一个节点的话就要继承一个MPxNode。如果要写一个变形器的话,那就要继承DeformerNode。
当然还有灯光材质或者其他的那些,我们要选择对应的代理类去继承然后使用。
比如,我们要编写一个新的节点的话,我们可以继承MPxNode,里面我们可以选择对应的类型,我们可以不用继承一个最基本的一个类型,比如我们要写一个新的Locator的话,我们可以直接定义他的初始化类型Locator。那么,这些方法我们都是要定义同样的方法,同样的参数,然后写我们不同的处理方法。Maya会自动访问这些方法,来根据我们的计算反映出不同的数值。compute方法是计算方法,是节点最核心的方法,我们所有的算法都要存在这里边然后返回不同的结果。
这只是一个单纯的节点,我们如果要编写一个新的节点的话,我们还需要在给这个节点他添加上我们所需要的属性,那么在compute方法里面,我们通过不同的算法,把不同的数值返回给我们的属性,那么它就会输出到其他的节点,从而达到我们真正所需要的想法。

总结

image.png

第二十一节MSelectionList与MItSelection List

MSelectionList

image.png
MSelectionList只是一个普通的节点列表。用来存储节点或者物体的列表。
MSelectionList类的方法:
isEmpty:用来判断实例化的对象是否是一个空的MSelectionList,返回布尔类型
add: 可以添加的类型有MObject、MDagPath、MString、MPlug、MUuid。其中MString可以是确切的物体的名字,也可以包含通配符的。例如add(“pSphere*”)就会挨个添加pSphere1、pSphere2等,这里的*就是通配符可以指代所有字符
length:返回列表中的元素个数
merge(合并) : 把参数中的列表内容添加到使用此方法的对象上。例如: ball_lst.merge(box_lst)就是将box_lst的内容添加到ball_lst列表里面
intersect(交集):同理merge得到两个列表(调用方法的对象和方法中的参数)的交集
remove:移除

MItSelectionList

MItSelectionList是SelectionList 的迭代器,用于逐个访问MSelectionList里的项目
生成迭代器需要传入MSelectionList对象
常用方法:
next: 通过next访问下一个元素
reset: 返回到某一位置重新继续next
getDagPath:获取里面的DagPath
getDependencyNode:获取里面的MObject
举例,迭代SelectionList里的内容
通过lst_iter = OpenMaya.MItSelectionList(sel_lst)来创建sel_lst的迭代器,然后迭代列表中的内容,输出dag_path
image.png
最重要的是框住的那些内容,如果不写这些会造成死循环,如果要迭代执行应该首先写这两个内容。

第二十二节MGlobal全局操作类

image.png
重点:不需要实例化
静态方法:
mayaVersion:返回当前maya的版本号
getActiveSelectionList(MSelectionList的对象):将我们在maya中选择的内容存入到传递的实例化的MSelectionList中(会替换列表中的内容,例如选择了5个物体执行命令列表会有5个,再选择1个物体执行命令列表会变成1个)。
setActiveSelectionList(MSelectionList的对象): 类似于select命令后面跟一组列表,将列表中的物体选择。
executeCommand:参数中写mel命令,可以通过api使用这些命令
executePythonCommand:参数中写python命令,可以通过api使用这些命令
isYAxisUp:判断场景的向上轴是否为Y轴,如果是返回True,如果不是返回False。同理isZAxisUp
displayInfo:显示输出信息,参数是字符串
displayWarning:输出显示警告信息image.png
displayError: 输出显示错误信息
viewFrame: 参数为整数,调整当前帧的位置
image.png

第二十三节MFileIO文件操作类

MFileIO同样是一个全局的操作类,使用它不需要实例化
currentFile: 返回当前的场景的路径
setCurrentFile: 更改当前场景的名字路径,参数为字符串
fileType: 返回当前场景文件类型
查看可以保存的文件类型(结果在列表中):
--------------------------------------------
lst = list()
OpenMaya.MFileIO.getFileTypes(lst)
[u’mayaAscii’, u’mayaBinary’, u’mel’, u’OBJ’, u’directory’, u’plug-in’, u’audio’, u’move’, u’EPS’, u’Adobe® Illustrator®’, u’image’, u’fluidCache’, u’editMA’, u’editMB’, u’IGES_ATF’, u’JT_ATF’, u’PARASOLID_ATF’, u’SAT_ATF’, u’STEP_ATF’, u’WIRE_ATF’, u’CATIAV4_ATF’, u’CATIAV5_ATF’, u’DWG_ATF’, u’DXF_ATF’, u’NX_ATF’, u’PROE_ATF’, u’IGES_ATF Export’, u’JT_ATF Export’, u’PARASOLID_ATF Export’, u’SAT_ATF Export’, u’STEP_ATF Export’, u’WIRE_ATF Export’, u’CATIAV5_ATF Export’, u’DWG_ATF Export’, u’DXF_ATF Export’, u’NX_ATF Export’, u’FBX’, u’FBX export’, u’DAE_FBX’, u’DAE_FBX export’, u’SVG’, u’ASS Export’, u’ASS’, u’Alembic’, u’OBJexport’, u’BIF’]
-------------------------------------------
newFile(True):创建一个新的场景(强制)
save:保存场景
saveAs:另存场景,第一个参数为保存路径,第二个参数为保存类型(例如’mayaBinary’,‘mayaAscii’)
exportSelected:导出选择的物体,也是两个参数,第一个参数为文件路径,第二个参数为文件类型
exportAll:导出所有物体。
image.png
image.png

第二十四节MFnDependencyNode与MItDependencyNodes

MFnDependencyNode

image.png
在maya中所有的物体都是以节点的形式来存在的,它们最基本的模式就是一个DependencyNode,带有层级的那些节点都是从DependencyNode扩展而来,变成了DagNode,通过不同的继承发展成不同的节点。
在maya中的所有的节点,都可以使用MFnDependencyNode方法
创建MFnDependencyNode的方法:
第一个:如果直接通过 MFnDependencyNode()语句创建,这样创建出来的不能与任何节点进行连接,创建出来的实际上是一个空的,没有任何意义。
第二个:通过传入一个MObject来实例化一个MFnDependencyNode,通过MObject,maya可以追踪到任何一个节点。
配合pymel快速传入MObject:
mfn = OpenMaya.MFnDependencyNode(pm.ls(sl=True)[0].apimobject())
MFnDependencyNode的常用方法:
typeName:返回节点类型
name:返回节点名字
setName(要更改的名字):更改节点的名字
attributeCount:查询节点有多少个属性
attribute:通过属性的名字返回MObject(指针)
findPlug:同样是返回属性
isLocked:判断是否锁定
hasAttribute :判断有没有属性
icon:节点查询图标
setIcon : 设置图标

MItDependencyNodes

MItDependencyNodes可以过滤场景中的所有基本节点,我们可以通过这个迭代器来达到我们命令中使用ls的这种效果。
iterator = OpenMaya.MItDependencyNodes(OpenMaya.MFn.kMesh) #创建一个迭代器对象,用来过滤场景中的所有多边形类型
在maya中所有的迭代器都有一个isDone方法用来判断迭代器是否已经迭代完成

1
2
3
4
5
6
7
8
import maya.OpenMaya as OpenMaya
# 创建一个过滤mesh类型的迭代器
iterator = OpenMaya.MItDependencyNodes(OpenMaya.MFn.kMesh)
while not iterator.isDone():
# 通过iterator.thisNode()得到迭代器中的节点(MObject)
# 然后将MObject给MFnDependencyNode来使用DependencyNode的方法(name)
print OpenMaya.MFnDependencyNode(iterator.thisNode()).name() # 输出场景中的多边形的名字
iterator.next()

第二十五节MFnDagNode和MItDag

MFnDagNode

image.png
MFnDagNode常用方法:
parent
child
hasParent
hasChild
isParentOf
isChildOf
dagPath
fullPathName
partialPathName

1
2
3
4
5
6
7
8
9
10
11
12
13
import maya.OpenMaya as OpenMaya
import pymel.core as pm
# pm.PyNode('pSphere1').__apiobject__()找到场景中类型为MDagPath的pSphere1
# 当指定后,即使后续更改物体的名字,maya依然能够找到对应的物体。
# api和pymel是一样的,都是直接绑定到节点上的,不依据字符串查找,即使节点发生变化依然可以追踪到。
mfn=OpenMaya.MFnDagNode(pm.PyNode('pSphere1').__apiobject__())
mfn.partialPathName() # 得到pSphere1的短名
mfn.fullPathName() # 得到pSphere1的长名
mfn.childCount() # 查询pSphere1的下一级有几个物体(包含shape)
mfn.child(0) # 得到pSphere1的第一个子物体 类型为MObject
mfn.parent(0) # 得到pSphere1的第一个父物体 类型为MObject
OpenMaya.MFnDagNode(mfn.parent(0)).partialPathName() # 输出pSphere1的第一个父物体的短名
mfn.removeChildAt(1) # 移除pSphere1的第二个子物体(第一个子物体是pSphereShape1),也会携带着移除第二个子物体的所有子级

MItDag

image.png

1
2
3
4
5
6
7
8
import maya.OpenMaya as OpenMaya
import pymel.core as pm
iterator = OpenMaya.MItDag()
# 将迭代器的迭代起始点为group4,方式为广度优先,迭代类型为mesh类型
iterator.reset(pm.PyNode('group4').__apiobject__(),OpenMaya.MItDag.kBreadthFirst,OpenMaya.MFn.kMesh)
while not iterator.isDone():
print iterator.partialPathName()
iterator.next()

其中迭代器的自带方法中的 partialPathName是返回的字符串,代表当前迭代项的名字,如果使用currentItem返回的是当前迭代项的MObject。

第二十六节MFnMesh与MItMesh

MFnMesh

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding:utf-8
import maya.OpenMaya as OpenMaya
import pymel.core as pm

mfn = OpenMaya.MFnMesh(pm.PyNode('pSphere1').__apiobject__())
mfn.numVertices() # 查询pSphere1有多少个顶点。
mfn.numEdges() # 查询有多少根线。
mfn.numPolygons() # 查询有多少个面。
point = OpenMaya.MPoint() # 定义一个空的MPoint类型的对象
mfn.getPoint(0, point) # 将pSphere1的序列号为0的顶点赋予刚才定义的MPoint类型的对象
print point.x, point.y, point.z # 输出赋予顶点后的point的xyz
point1 = OpenMaya.MPoint(0, 1, 0) # 定义一个携带数值的MPoint类型的对象
mfn.setPoint(381, point1) # 将pSphere1的序列号为381的顶点的数值设置为point1携带的数值

MItMesh

MItMesh有四种类型分别是MItMeshPolygon,MItMeshVertex,MItMeshEdge,MItMeshFaceVertex
用的最多的是Vertex,polygon,edge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# coding:utf-8
import maya.OpenMaya as OpenMaya
import pymel.core as pm

iterator = OpenMaya.MItMeshVertex(pm.PyNode('pSphereShape1').__apimobject__()) # 使用apimobject得到MObject类型的对象
while not iterator.isDone():
print iterator.index() # 得到迭代器中的顶点的序列号

point = iterator.position() # 通过position方法得到携带位置信息的MPoint类型的顶点,然后赋予point
print point.x, point.y, point.z # 配合while输出每个顶点的位置信息
# 将所有顶点的位置设置为0,0,0
point1 = OpenMaya.MPoint(0, 0, 0)
iterator.setPosition(point1)
iterator.next()

第二十七节PythonApi与指针

有些方法需要传递带类型的指针,因此如果使用python语句就需要通过MScriptUtil来定义对应类型的指针。
image.png带有Util的一般都是通用的工具函数
有些方法是需要传入 float2 类型的指针,为了能够在python中使用因此需要通过MScriptUtil
image.png
MScriptUtil的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# coding:utf-8
import maya.OpenMaya as OpenMaya
import pymel.core as pm

iterator = OpenMaya.MItMeshVertex(pm.PyNode('pSphereShape1').__apimobject__()) # 使用apimobject得到MObject类型的对象

s_util = OpenMaya.MScriptUtil() # 定义一个MScriptUtil
uv_ptr = s_util.asFloat2Ptr() # 通过使用MScriptUtil中的方法定义一个float2类型的指针

while not iterator.isDone():
iterator.getUV(uv_ptr) # 将顶点的UV值传递给自定义的float2类型的uv_ptr指针
print s_util.getFloat2ArrayItem(uv_ptr, 0, 0), # 输出指针中的0,0对应的值(u坐标)
print s_util.getFloat2ArrayItem(uv_ptr, 0, 1) # 输出指针中的0,1对应的值(v坐标)
iterator.next()

根据所需要的指针类型加上as就可以直接得到了。
如果要在指针中求值就需要使用get开头的方法,传进去指针就可以了。
image.png

第二十八节MayaAPI事件触发 - MMessage

image.png
要操作物体层级相关的,就用MDagMessage
要操作普通节点相关的,就用MDGMessage
调用命令的时候就用MCommandMessage
要绑定某一个节点上的事件,比如说属性变化时可以使用MNodeMessage
可以使用多边形的MPolyMessage
场景变化或更新时可以使用MSceneMessage

案例MTimerMessage

首先了解一下MTimerMessage的一个方法,addTimerCallback,其中callback的意思是回调函数,什么是回调函数?作为参数传递的那个函数就被叫为回调函数。
image.png

1
2
3
4
5
6
7
# coding:utf-8
import maya.OpenMaya as OpenMaya
def func(*args): # 定义回调函数,输出1以及传入的参数(传入的第一个是间隔时间,第二个是持续时间)
print 1,args
callback_id = OpenMaya.MTimerMessage.addTimerCallback(2,func) # 每隔两秒执行一次定义的回调函数
OpenMaya.MTimerMessage.removeCallback(callback_id) # 移除刚才定义的定时器

案例MEventMessage

MEventMessage的addEventCallback方法所需要的参数:
第一个参数是事件名字。

image.png
MEventMessage通过事件触发,而这些事件都是哪些可以通过getEventNames来得到:
得到MEventMessage所支持的事件:

1
2
3
4
5
# coding:utf-8
import maya.OpenMaya as OpenMaya
event_names = list() # 定义一个空列表用来存取事件名字
OpenMaya.MEventMessage.getEventNames(event_names) # 将事件名字存入到列表event_names中
print event_names # 输出列表

其中常用的事件:
deleteAll,undoSupressed(撤销后的返回),undo(撤销),timeChanged(时间轴变化)
其他事件的解释可以通过scriptjob命令的帮助文档找到:
https://help.autodesk.com/view/MAYAUL/2018/ENU/?guid=__CommandsPython_index_html

1
2
3
4
5
6
# coding:utf-8
import maya.OpenMaya as OpenMaya
def func(*args): # 定义回调函数,输出1以及传入的参数(传入的第一个是间隔时间,第二个是持续时间)
print 1,args
callback_id = OpenMaya.MEventMessage.addEventCallback('timeChanged',func) # 当时间轴变化时触发回调函数
OpenMaya.MEventMessage.removeCallback(callback_id) # 移除刚才定义事件触发器

练习

第一节

image.png
circle;
circle -r 2;
circle - r 2 -nr 0 1 0;
curve -d 1 -p -2 0 -2 -p 3 0 -2 -p 3 0 3 -p -2 0 3 -p -2 0 -2;

1
2
3
4
5
6
import maya.cmds as mc
mc.circle()
mc.circle(r=2)
mc.circle(r=2,nr=(0,1,0))
mc.curve(d=1,p=[(-2,0,-2),(3,0,-2),(3,0,3),(-2,0,3),(-2,0,-2)])

第二节

image.png
这个跟课中讲的批量重命名应该差不多。

1
2
3
4
import maya.cmds as mc
for shape in mc.ls(typ='mesh'):
mc.rename(shape,'mesh_{0}'.format(shape))

第五节

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import maya.cmds as mc

sel = mc.ls(sl=1)
info = []
info_str = ''
for i in sel:
posx = mc.getAttr(i + '.tx')
posy = mc.getAttr(i + '.ty')
posz = mc.getAttr(i + '.tz')
type = mc.objectType(i)
info.append([i, posx, posy, posz,type])
for i in info:
info_str = info_str + i[0]+' type:'+i[4] + '\r\nposx:' + str(i[1]) + '\r\nposy:' + str(i[2]) + '\r\nposz:' + str(
i[3]) + '\r\n\r\n'
f = open('D:\\posInfo.txt', 'w')
f.write(info_str)
f.close()

第七节

image.png

1
2
3
4
5
6
7
8
import maya.cmds as mc
for i in range(3) :
lam=mc.createNode('lambert')
mc.createNode('blendColors')
mc.connectAttr('lambert2.oc','blendColors1.c1')
mc.connectAttr('lambert3.oc','blendColors1.c2')
mc.connectAttr('blendColors1.output','lambert4.c')

第十节

image.png

1
2
3
4
5
6
7
8
9
import maya.cmds as mc
import random
for i in range(200):
pox=random.randrange(-10,10)
poy=random.randrange(-10,10)
poz=random.randrange(-10,10)
mc.setKeyframe('pSphere1',at='tx',t=i,v=pox)
mc.setKeyframe('pSphere1',at='ty',t=i,v=poy)
mc.setKeyframe('pSphere1',at='tz',t=i,v=poz)

第十二节

image.png

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
import maya.cmds as mc
def ShowWindow():
wnd_name='my_window'
if mc.window(wnd_name,q=True,ex=True):
mc.deleteUI(wnd_name,wnd=True)
if mc.windowPref(wnd_name,q=True,ex=True):
mc.windowPref(wnd_name,r=True)
wnd = mc.window(wnd_name,w=400,h=300,t='My Window')
mc.gridLayout(numberOfColumns=2, cellWidth=200)
mc.text(l='num:')
mc.intField('numInt')
mc.text(l='radius:')
mc.floatField('radiusFloat')
mc.button('ok',command=onCreateClick)
mc.showWindow(wnd)
def onCreateClick(*args):
num=mc.intField('numInt',q=True,v=True)
radius=mc.floatField('radiusFloat',q=True,v=True)
create(num,radius)

def create(num,radius):
for i in range(num):
mc.polySphere(r=radius)
ShowWindow()


image.png通过点击ok根据num和radius创建对应的球体