This batch renaming utility elaboration has been a long journey. I did a simple one long time ago which was supporting only 1 renaming pattern : http://pythoncvc.net/?p=27
Principle
We use parameters available in our views to rename them in a programmatic and logic way. My old script was using an home parameter for discipline and sub-discipline which is set by view template, level and scope box name to make it unique as much as possible.
Objectives
Flexibility : clients, contractors or someone else may ask a specific naming pattern for views. So pattern need to be easily adaptable and savable.
Multilingual : as always, to avoid language dependent behaviour, when it is possible built in parameter name or shared parameter Guid shall be used to retrieve values.
Human readable : parameters storage type may be text, number, id etc… and have a better human readable AsValueString (it is the case for scope boxe).
Free separators choice : we should be able to freely choose and change separator, prefix, suffix between parameters
Flexible selection of view to rename : be able to rename X views then select Y other views and rename it and so on.
Code behind in short
Renaming part
I was thinking about common file re-namers like Ant Renamer. User is able to type parameters as a simple string and add anything he wants between them. The string looks like the old string formatting style with %s %d etc…
So my first attempt was to use python string format like "string{Guid.<Guid>}_{bip.<BuiltInParameterName>_{<parameter_name>}".format(parameter_by_Guid.value, parameter_by_builtinparameter.value, parameter_by_name.value)
. That was working but somehow it felt weird and slow.
I finally used regular expressions which are very much used for this kind of task in a very efficient way (spelling correction, retrieve a phone number, verify that an email entered by a user looks like one etc…).
So we needed a regular expression to retrieve parameters by Guid :
# Create a regular expression to retrieve Guid (including constructor) in a string parameterGuidRegex = re.compile(r''' Guid\( # Guid constructor. Example : Guid("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4") [\" | \'] # Single or double quote required in Guid constructor ([0-9a-f]{8} # First 8 hexa (-?[0-9a-f]{4}){3} # Optional separator + 4 hexa 3 times -?[0-9a-f]{12}) # Optional separator + 12 hexa [\" | \'] # Single or double quote required in Guid constructor \) # Close parenthesis necessary to build a Guid ''', re.IGNORECASE | re.VERBOSE)
… by BuiltInParameter (way far simpler) :
# Create a regular expression to retrieve BuiltInParameter in a string parameterBipRegex = re.compile(r"bip\((\w+)\)")
… and finally by name :
# Create a regular expression to retrieve named parameters in a string parameterNameRegex = re.compile(r"name\(([\w\s]+)\)") # Retrieve group(1)
There is mainly 3 class of view (excluding schedules, legends etc…) : ViewPlan, View3D, ViewSection. Each kind don’t have the same available parameter. For example there is no level parameter in 3D and section views. So 3 pattern definition was required to batch rename views. To show available parameters to user a sample view of each class is retrieved and parameters Guid, BuiltInParameterName, and Name are stored as a dictionary.
To help user add parameter to pattern the best way. A function try to retrieve Guid else BuiltInParameter name else name.
Another function replace pattern by their associated parameter name and value to show user a preview.
A last function is in charge of actually renaming views.
GUI part
A WPF GUI splitted in 4 part, 3 for each view classes, 1 for saving and validation options.
Saving/loading part
Thanks again to Ehsan, there is a very easy way to save script parameter to pyRevit config file. Don’t forget to encode and decode your string !
my_config = this_script.config # Then in WPF class we have following method : def save_config_click(self, sender, e): for view_class in self.view_class_dict.keys(): view_class_checkbox = eval('self.cb_{}'.format(view_class)) if view_class_checkbox.IsChecked: pattern_textbox = eval('self.{}_pattern'.format(view_class)) setattr(my_config, view_class, pattern_textbox.Text.encode('utf8')) this_script.save_config()
But I wanted also to be able to save to a parameter for project specific naming rules. Unfortunately with Revit API it is still currently not possible to create a project parameter which is not shared. So based on Spiderinnet samples I wrote a function to create a shared parameter and store configuration in it as a string which is formatted in a dictionary/json style.
When script is launched. It tries to load configuration from project parameter (as project specific case should be use first) else from pyRevit config file (the way you name it on your office) else from default values (which are for most people just samples).
Modeless / ExternalEvent part
To be able to rename successively multiple selections of views it was mandatory to use a modelless WPF window and so to use ExternalEvent for any Transaction to Revit : create shared parameter, save to parameter, rename views. I described how to do so in a previous article but as there was 3 different action to make it was a good idea to enhance it. I made 2 class to be able to execute any function or method through the external event using *args and *kwargs :
class CustomizableEvent: def __init__(self): self.function_or_method = None self.args = () self.kwargs = {} def raised_method(self): self.function_or_method(*self.args, **self.kwargs) def raise_event(self, function_or_method, *args, **kwargs): self.args = args self.kwargs = kwargs self.function_or_method = function_or_method custom_event.Raise() customizable_event = CustomizableEvent() # Create a subclass of IExternalEventHandler class CustomHandler(IExternalEventHandler): """Input : function or method. Execute input in a IExternalEventHandler""" # Execute method run in Revit API environment. # noinspection PyPep8Naming, PyUnusedLocal def Execute(self, application): try: customizable_event.raised_method() except InvalidOperationException: # If you don't catch this exeption Revit may crash. print "InvalidOperationException catched" # noinspection PyMethodMayBeStatic, PyPep8Naming def GetName(self): return "Execute an function or method in a IExternalHandler" # Create an handler instance and his associated ExternalEvent custom_handler = CustomHandler() custom_event = ExternalEvent.Create(custom_handler)
Full source code
As usual full source code is available on github.
Video demo
To learn more about
Thanks to all these resources which helped a lot
WPF tutorial :
string format :
Regular expressions :
https://www.udemy.com/automate/learn/v4/overview
https://automatetheboringstuff.com/chapter7/
https://docs.python.org/3.10/library/re.html
https://www.pcwdld.com/python-regex-cheat-sheet
Encoding :
http://sametmax.com/lencoding-en-python-une-bonne-fois-pour-toute/
Docstring :
http://sametmax.com/lencoding-en-python-une-bonne-fois-pour-toute/
https://www.python.org/dev/peps/pep-0257/
*args and **kwargs :