一半君的总结纸

听话只听一半君

#94 MEL里常用的正则表达式(Regular Expressions)

得到物体的名字,去掉属性或者component名字

string $node = "pCube1.f[2]";
string $no_component = `match "^[^\.]*" $node`;
// Result: "pCube1" //

得到component或者attribute的名字, 包括 ‘.’

string $node = "pCube1.f[2]";
string $component = `match "\\..*" $node`;
// Result: ".f[2]" //

string $nodeAttr = "blinn1.color";
string $attrName = `match "\\..*" $nodeAttr`;
// Result: ".color" //

得到 attribute 的名字, 不带 ‘.’

string $node = "pCube1.f[2]";
string $component = `substitute "^[^.]*\\." $node ""`;
// Result: "f[2]" //

string $nodeAttr = "blinn1.color";
string $attrName = `substitute "^[^.]*\\." $nodeAttr ""`;
// Result: "color" //

从完整路径得到父UI的名字

string $uiControl = "OptionBoxWindow|formLayout52|formLayout55|button6";
string $uiParent = `substitute "|[^|]*$" $uiControl ""`;
// Result: OptionBoxWindow|formLayout52|formLayout55 //

去掉末尾的回车键, 如果有的话

当你用‘fgetline’从文本文件读取一行的时候,下面这个可能会用到.

string $input = "line\n";
$string $line = `match "^[^(\r\n)]*" $input`;
// Result: "line" //

从文件路径中得到文件夹路径

注意下面的结果最后一个字符是/,通常这正是我们想要的,因为你再往后加文件名的时候就不用自己再加/了,此外有些命令比如 ‘getFileList’ 也要求参数的路径是/结尾的.

string $path = "C:/AW/Maya5.0/bin/maya.exe";
string $dir = `match "^.*/" $path`;
// Result: "C:/AW/Maya5.0/bin/"

从文件路径中得到文件名

string $path = "C:/AW/Maya5.0/bin/maya.exe";
string $filepart = `match "[^/\\]*$" $path`;
// Result: "maya.exe"

去掉数字后缀

string $node = "pCube1|pCubeShape223";
string $noSuffix = `match ".*[^0-9]" $node`;
// Result: "pCube1|pCubeShape"

得到数字后缀

string $node = "pCube1|pCubeShape223";
string $suffix = `match "[0-9]+$" $node`;
// Result: "223" //

从DAG 节点完整路径或者UI control完整 路径得到短的名字(最后一个|后面的部分)

string $dagPath = "pCube1|pCubeShape223";
string $shortName = `match "[^|]*$" $dagPath`;
// Result: pCubeShape223 //

11 May 2003

加强版:

现在已经是Python的时代了,所以上面的有点过时(但也并不全是),感觉上python的re module支持的regex的语法应该比MEL的要全不少。
名词缩写: re或者regex –> regular expression 正则表达式
简单理解就是 , 从一个长字符串里直接一步找到你想要的部分,所以mel里的match,tokenize等命令都用不着了
举例:

# 首先你要import re模块
import re

# 得到物体的名字,去掉属性或者component名字
node = 'pCube1.f[2]'
re.search('^[^\.]*',node).group()
# Result: pCube1 #

# 得到component或者attribute的名字, 包括 '.'
re.search('\\..*',node).group()
# Result: .f[2] #

nodeAttr = 'blinn1.color'
re.search('\\..*',nodeAttr).group()
# Result: .color #

# 得到 attribute 的名字, 不带 '.',这里使用了特殊的(?<=..) 这个叫做positive lookbehind assertion
re.search('(?<=\.)[^\.]*',node).group()
# Result: f[2] #

re.search('(?<=\.)[^\.]*',nodeAttr).group()
# Result: color #

# 从完整路径得到父UI的名字,这里用到了特殊的(?=...) 这个叫做lookahead assertion
uiControl = "‘OptionBoxWindow|formLayout52|formLayout55|button6’
re.search('^.*(?=\|)',uiControl).group()
# Result: OptionBoxWindow|formLayout52|formLayout55 #

# 去掉末尾的回车键, 如果有的话
# 当你用‘fgetline’从文本文件读取一行的时候,下面这个可能会用到.
input = 'line\n'
re.search('^[^(\r\n)]*',input).group()
# Result: line #

# 从文件路径中得到文件夹路径
# 注意下面的结果最后一个字符是/,通常这正是我们想要的,因为你再往后加文件名的时候就不用自己再加/了,此外有些命令比如 ‘getFileList’ 也要求参数的路径是/结尾的.
path = 'C:/AW/Maya5.0/bin/maya.exe'
re.search('^.*/',path).group()
# Result: C:/AW/Maya5.0/bin/ #

# 从文件路径中得到文件名
re.search('[^/\\\]*$',path).group()
# Result: maya.exe #

# 去掉数字后缀
node = 'pCube1|pCubeShape223'
re.search('.*[^\d]',node).group()
# Result: pCube1|pCubeShape #

# 得到数字后缀
re.search('\d+$',node).group()
# Result: 223 #

# 从DAG 节点完整路径或者UI control完整 路径得到短的名字(最后一个|后面的部分)
dagPath = 'pCube1|pCubeShape223'
re.search('[^|]*$',dagPath).group()
# Result: pCubeShape223 #

其实如果你用pymel,上面有好几个例子就用不着regex了,比如:

# 得到物体的名字,去掉属性或者component名字
node = pm.PyNode('pCube1.f[2]')
node.node().getParent()
# Result: nt.Transform(u'pCube1') #

... 等等

搜索替换reference的rig路径

举个长点的例子以体现regex的nb之处:

# 假设你有个rig的相对路径是 /proj/abc/share/char/fatlady/Rig/wip/090313/fatlady.300.r20.mayaHiFatlady3PP.0.mb
# 因为外包给别的公司了,他们的路径和你的不一样,你有几百个.ma文件需要把reference的rig的路径改成下面的
#  assets/fatLady/fatlady_300/300.20/maya/scenes/fatlady.300.r20.mayaHiFatLady.0.mb
# 你想一次拿到 fileName,asset名字,version,revision , 那么你可以

fileName,asset,ver,rev = re.search('(?<=/)((\D+)\.(\d+)\.r(\d+)\.(?:[^/]+))$',filePath).groups()
remappedPath = 'assets/{asset}/{asset}_{ver}/{ver}.{rev}/maya/scenes/{fileName}'.format(**locals())

()内的部分如果有match,就会成为一个group,上面的regex的意思是
(?<=/) 以/开头,但是不包含在结果里
( (\D+) 不是数字的多个字符
 \. 一个.
(\d+) 多个数字的字符
\. 一个.
r 字母r
(\d+) 多个数字字符
\. 一个.
(?:[^/]+) 多个非/的字符,但是不包含在结果里
)
$ match到最后

# 测试一下
filePath = '/proj/abc/share/char/fatlady/Rig/wip/090313/fatlady.300.r20.mayaHiFatlady3PP.0.mb'
fileName,asset,ver,rev = re.search('(?<=/)((\D+)\.(\d+)\.r(\d+)\.(?:[^/]+))$',filePath).groups()

print fileName,asset,ver,rev
fatlady.300.r20.mayaHiFatlady3PP.0.mb fatlady 300 20

remappedPath = 'assets/{asset}/{asset}_{ver}/{ver}.{rev}/maya/scenes/{fileName}'.format(**locals())

print remappedPath
assets/fatlady/fatlady_300/300.20/maya/scenes/fatlady.300.r20.mayaHiFatlady3PP.0.mb

不打开Maya直接load当前是unload状态的referece

再举个例子,比如你想不打开.ma文件,把里面某些已经是unloaded状态的reference给load了,意思就是当下次打开此文件的时候,reference editor里这个reference是勾上了的,而现在是没有勾的。这么做的意义何在? 比如你场景很大,当animator工作的的时候他肯定觉得卡,如果场景里有30多个character,他肯定是其他的都unloaded了,为了k动画的时候快,但是如果他做好了以后,或者他想提交一个dailies review的movie,他肯定得显示全部的角色,然后使用类似”playblast”的功能对把,这个时候,如果他load全部,也许load一个角色就要5分钟,更不用说可能他还要赋予texture之类的操作。这些操作是可以自动的,比如你可以用一个命令行工具,让animator对他的文件运行

makeDailies myScene.ma

然后让这个工具做以下事项

  1. 另存一个文件比如myScene.AUTO.DAILIES.ma
  2. unload 场景或是不需要的部分
  3. load 所有的character
  4. update所有的rigs
  5. assign texture?(animator的工作文件里没有texture,仅在需要的时候他们才自己assign临时的)
  6. 进行类似”playblast”的操作

这里这个例子没举好,当然你可以说我可以用maya -batch 把.ma文件直接打开,然后再勾上,这样完全可以,但是设想以下情形,如果场景文件很大了,而dailies只看其中一组角色,我意思是比如有30个character, 其中20个已经dailies通过了,有5个,他们和其他char没有交互,也就是说单独看他们就行了,不用playblast其他的char,这时候,你是不是希望只load这5个char? (lz想说的是,这个例子依然没举好,但是也许在你的屌丝民工生涯中有某个时候你会希望不开Maya gui而unload某些char的…) 如果是你可以像下面这么做(显然最终版playblast你还是得load全部char的,那时候你完全可以自动开maya,然后勾上全部)

因为.ma是一个文本文件,所以你可以打开他直接编辑,而打开后你会发现他其实整个都是MEL命令组成的,在最上面你会发现reference了哪些文件是由file命令控制的:

file -r -ns "FLD1" -dr 1 -rfn "FLD1RN" "assets/fatlady/fatlady_100/100.73/maya/scenes/fatlady.100.r73.mayaHiFatlady.0.mb";

这一行里如果你去MEL DOC里看file的帮助,你会找到

  • -dr 表示 delayed reference 也就是在开文件的时候不要load
  • -r 表示reference文件 “assets/fatlady/fatlady_100/100.73/maya/scenes/fatlady.100.r73.mayaHiFatlady.0.mb”
  • -ns 表示namespace是 FLD1
  • -rfn 表示 reference node是 FLD1RN

好了,也就是说,如果你把 -dr 1 去掉变成

file -r -ns "FLD1" -rfn "FLD1RN" "assets/fatlady/fatlady_100/100.73/maya/scenes/fatlady.100.r73.mayaHiFatlady.0.mb";

那下次你开文件的时候,这个reference 就会是勾上的了,(如果有嵌套(nested reference)情形的时候会复杂点),你可以手动编辑保存了试试。
那么如果自动替换呢,这时候终于regex上场了,略去开文件,读取一行的部分(注意当这一行过长的时候,在.ma文件里可能不是一行)

line = 'file -r -ns "FLD1" -rfn "FLD1RN" "assets/fatlady/fatlady_100/100.73/maya/scenes/fatlady.100.r73.mayaHiFatlady.0.mb"'
newLine = re.sub('(?<= )-dr 1 (?=-rfn)', '', line)  >>> print newLine
'file -r -ns "FLD1" -rfn "FLD1RN" "assets/fatlady/fatlady_100/100.73/maya/scenes/fatlady.100.r73.mayaHiFatlady.0.mb"'

上面的 (?<= ) 表示在-dr 1的前面是个空格,但是不把空格包括在结果里,这个叫做positive lookahead assertion
而(?=-rfn)表示在 -dr 1 后面是-rfn,但是不把-rfn包括在结果里,这个叫做lookahead assertion
所以 上面的re.sub()找到了-dr 1 并把他替换为”,再附给了newLine

你只要把新结果写回.ma文件即可.(有个稍微更完整点的例子参见 改进版的mayaRefParser )

除了re.search这个用来搜字符串的以外,还有re.match(来看一个字符串是不是完全符合给定的规则) re.sub(搜索到满足规则的部分就把他替换成给定的字符串),详细帮助和re比match支持的更多的regex特性见:
http://docs.python.org/2/howto/regex.html
http://docs.python.org/2/library/re.html

如果想在线测试一下,可以试试这个 http://rubular.com/ 虽然可能对regex的支持度和python稍有不同,不过如果不是很长很复杂的,应该差不多

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: