This is a naming script that i use to name nodes in Autodesk Maya. This particular script however doesnt use anything maya specific.
I had asked a while ago how I would go about doing something like this, where a variable convention could be used, and templating came up.
So if i had a convention like this:
'${prefix}_${name}_${side}_${type}'
I could pass these arguments:
bind_thigh_left_joint
And then run them through an abbreviation dictionary (as well as a user abbreviation dictionary), check it with relevant nodes in scene file to make sure there are no duplicates, and end up with this: bn_thigh_L_jnt
However I wanted it so that if one of the keys has a first uppercase letter, it would make the substitute uppercase.
For example if {$prefix}
was instead {$Prefix}
thigh would become Thigh, or if {$prefix}
was {$PREFIX}
thigh would become THIGH. However if it was {$PREfix}
thigh would still only be Thigh.
I can do this easily enough except that I have no way to detect the individual cases of the keys. For example, if the string is '${Prefix}_${name}_${SIDE}_${type}'
How would I find what case prefix is, and if i knew that, how would i use that with this template?
Note this code isnt the exact code i have, I have ommitted a lot of other stuff that was more maya specific, this just deals with the substituting itself.
from string import Template
import collections
def convert(prefix, name, side, obj_type):
user_conv = '${Prefix}_${name}_${SIDE}_${type}'
# Assigns keys to strings to be used by the user dictionary.
subs = {'prefix': prefix, 'name': name, 'side': side, 'type': obj_type}
# Converts all of user convention to lowercase, and substitutes the names from subs.
new_name = Template(user_conv.lower())
new_name = new_name.safe_substitute(**subs)
# Strips leading and trailing underscores, and replaces double underscores with a single
new_name = new_name.strip('_')
new_name = new_name.replace('__', '_')
return new_name
print convert('bind', 'thigh', 'left', 'joint')
>> bind_thigh_left_joint
Edit: Also would like to strip multiple underscores
So if I had something like:
'${prefix}___${name}__${side}_____${type}'
I would want it to come out
>> bind_thigh_left_joint
not
>> bind___thigh__left____joint
Also the last thing, I figured since a user would be inputting this, it would be more convenient not to be adding brackets and dollar signs. Would it be possible to do something like this?
import re
user_conv = 'PREFIX_name_Side_TYPE01'
# do all filtering, removing of underscores and special characters
templates = ['prefix', 'name', 'side', 'type']
for template in templates:
if template in user_conv.lower():
# add bracket and dollar sign around match
>> '${PREFIX}_{name}_{Side}_${TYPE}01'
Here, we can use the power of OOP to make the template do what we want to. We can go ahead and extend the string.Template
class (as suggested in the docs).
Let us first import some relevant methods/classes:
from string import Template, uppercase, _multimap
import collections
We then define a helper method for dealing with arguments passed to the safe_substitute()
or substitute()
method. (The meat for this method was taken from Python's string
module source):
def get_mapping_from_args(*args, **kws):
if len(args) > 1:
raise TypeError('Too many positional arguments')
if not args:
mapping = kws
elif kws:
mapping = _multimap(kws, args[0])
else:
mapping = args[0]
return mapping
We then go ahead and define our extended Template class. Let us call this class CustomRenameTemplate
. We write a helper method called do_template_based_capitalization()
, that basically does the capitalization based on the template pattern you have provided. We make sure we override the substitute()
and safe_substitute()
methods to use this.
class CustomRenameTemplate(Template):
def __init__(self, *args, **kws):
super(CustomRenameTemplate, self).__init__(*args, **kws)
self.orig_template = self.template
self.template = self.template.lower()
def do_template_based_capitalization(self, mapping):
matches = self.pattern.findall(self.orig_template)
for match in matches:
keyword = match[self.pattern.groupindex['braced']-1]
if keyword[0] in uppercase: # First letter is CAPITALIZED
if keyword == keyword.upper(): # Condition for full capitalization
mapping[keyword.lower()] = mapping[keyword.lower()].upper()
else: # Condition for only first letter capitalization
mapping[keyword.lower()] = mapping[keyword.lower()].capitalize()
def safe_substitute(self, *args, **kws):
mapping = get_mapping_from_args(*args, **kws)
self.do_template_based_capitalization(mapping)
return super(CustomRenameTemplate, self).safe_substitute(mapping)
def substitute(self, *args, **kws):
mapping = get_mapping_from_args(*args, **kws)
self.do_template_based_capitalization(mapping)
return super(CustomRenameTemplate, self).substitute(mapping)
We are ready to use this class now. We go ahead and do some slight modifications to your convert()
method to put this new class into action:
def convert(prefix, name, side, obj_type, user_conv='${Prefix}_${name}_${SIDE}_${type}'):
# Let us parameterize user_conv instead of hardcoding it.
# That makes for better testing, modularity and all that good stuff.
# user_conv = '${Prefix}_${name}_${SIDE}_${type}'
# Assigns keys to strings to be used by the user dictionary.
subs = {'prefix': prefix, 'name': name, 'side': side, 'type': obj_type}
# Converts all of user convention to lowercase, and substitutes the names from subs.
new_name = CustomRenameTemplate(user_conv) # Send the actual template, instead of it's lower()
new_name = new_name.substitute(**subs)
# Strips leading and trailing underscores, and replaces double underscores with a single
new_name = new_name.strip('_')
new_name = new_name.replace('__', '_')
return new_name
And here's it in action:
>>>print convert('bind', 'thigh', 'left', 'joint')
Bind_thigh_LEFT_joint
>>>print convert('bind', 'thigh', 'left', 'joint', user_conv='${prefix}_${name}_${side}_${type}')
bind_thigh_left_joint
>>>print convert('bind', 'thigh', 'left', 'joint', user_conv='${prefix}_${NAme}_${side}_${TYPE}')
bind_Thigh_left_JOINT
If you want to deal with multiple occurrences of the underscore _
and possible special characters in the user convention, just add the following lines before the return
statement of the convert()
method:
new_name = re.sub('[^A-Za-z0-9_]+', '', new_name) # This will strip every character NOT ( NOT is denoted by the leading ^) enclosed in the []
new_name = re.sub('_+', '_', new_name) # This will replace one or more occurrences of _ with a single _
Note:
An important thing to consider when stripping away special characters is the special characters used by Maya egs. for namespace representation :
and for hierarchy representation |
. I will leave it up to you to either choose to strip these away, or replace them with another character, or to not receive them in the first place. Most Maya commands that return an object name(s) have flags to control the verbosity of the name returned (i.e. egs. WITH namespace, full DAG path, or none of these).
For the extended portion of your question, where you had asked:
Also the last thing, I figured since a user would be inputting this, it would be more convenient not to be adding brackets and dollar signs. Would it be possible to do something like this?
Yes. In fact, to generalize that further, if you assume that the template strings will only by alpha and not alpha-numeric, you can again use re
to pick them up from the user_conv
and stuff them inside ${}
like so:
user_conv = 'PREFIX_name_Side_TYPE01'
user_conv = re.sub('[A-Za-z]+', '${\g<0>}', user_conv)
>>> print user_conv
>>> ${PREFIX}_${name}_${Side}_${TYPE}01
We used the power of backreferences here i.e. with \g<group_number>
. Check the docs here for more information on backreferences in regular expressions.
Create duplicate substitutions for each capitalization you want to support. Loop over the key/value pairs in your original subs
dictionary with dict.items()
or dict.iteritems()
. 'KEY': 'VALUE'
and 'Key': 'Value'
pairs are easy to create with .upper()
and .title()
.
If supporting 'KEy': 'Value'
is really important to you, that can be done by looping over indices of the key, splitting, upper-casing the first part, and re-combining. For example, if key
is 'Hello'
,
key[:2].upper() + key[2:]
will be 'HEllo'
.
Then, just use safe_substitute
as normal.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With