Auto Name Views on Sheets - PyRevit vs plugin?

Hello Dynamo Friends :slight_smile:

I am using a dynamo script to name views on a sheet with sheet name + “Section” + detail number. This for sure won´t auto update.
A company i worked at hat a similar plugin, that auto updated the view name with “@sheetnumber”, but didn´t work that well.

There are even plugins in the autodesk store that can auto name views: eganbimresources

For sure i want to build this on my own but it´s not the time for me to build my first plugin. So i stumbled across this post from @GavinCrump

So maybe it´s time for me to start using pyRevit?! Would it be possible to rename views always when a view is placed on a sheet, removed from a sheet or if the detail number is changed? What will the other users have to do to use the code. Installing pyRevit for sure, but will they have to do additional steps like manually activate that python script every time?

Doable in pyRevit or is this a classic plugin case?

Happy about any advice :slight_smile:
Kind Regards

Edit: Additional question, is this the event I´m after? ViewActivated Event
And will this also be triggert when changing the detail number?

I am not a user of pyRevit, but it should certainly be possible to use Events, however I would recommend that you look at Dynamic Updaters for what your trying to do. We have similar things in place (custom add-in) for when views are placed on sheets or views are created etc.

Edit:
I guess one more thing to mention is that although it will be leveraging pyRevit, what your after is still going to be custom coding a solution tailored for your use case. So, unless you use pyRevit for other things as well, a fully custom add-in could be more lightweight, and user friendly.

1 Like

Second Sean’s thoughts. If you’re well versed in addin deployment and not deploying pyrevit then an addin is fine. If you’re more versed in Python and have capability to deploy pyrevit then a view changed or activated event might be infrequent enough to safely run this check. You could always run it pre-sync alternatively so that before a user returns their wrongly named views they get fixed and synced in.

1 Like

I think the python way should take me a week while the plugin method will take ma a year :smiley:
So I will definitely start with the python way and see how it´s going. Never heard of the Dynamic Model Updater, thanks for that.
Thanks for your replies!

Correct View Names are not really necessary for our workflow, we don´t need them to build filters or any other important stuff. So renaming views at syncing might be pretty much all i need.

1 Like

pyRevit is not a heavy weight application. it is really more of a platform than an application. Very fast and easy to set up. Can be installed at either the admin level or at the user level.

pyRevit uses “hooks” which provide access to Revit events. Much simpler to set up than an add-in.

Whether an add-in or pyRevit, I would plan event driven applications carefully. They need to be robust and foolproof. You have a limited list of events to choose from and you don’t want your app to be firing all the time and slowing down Revit. So don’t just add it to the DocumentChanged event unless you know it will take a minuscule nanosecond to run. Even a view change event needs to be carefully thought out.

pyRevit can do what you want.

I’ve got my view naming and placement management setup to run on save and sync. At those points it tidies up everything the user has done since the last save. (Plus north arrows on plan sheets, graphic scales, checks door numbers, sets existing doors to 45 degrees, etc, etc.) Again - program with execution time in mind. No one wants a 5 minute save because your programing is slow.

Don’t forget to give users some notification of what has done and log your actions for trouble shooting. This is the toaster form in pyRevit that you can click on and get a log of what was accomplished. no popup toaster - and the user knows something is wrong.

image

1 Like

Thanks for your reply aaron, I´m already making my first steps with pyrevit.

I have some more questions:
In pyRevit i can load python scripts that wll appear as buttons in the pyRevit Tab.
Is it correct that i can also use pyRevit to create a new Tab for my scripts, so that it looks like a real plugin?

And in the following thread someone says that writing a plugin with python is also possible without pyRevit. How would that work? Still so much things i don`t understand^^

Kind Regards!

Maybe check out my videos here which should how to set up a tab. Noting this is really more suited to the pyrevit forums vs Dynamo if we’re heading down this discussion path:

To run Python through Revit you generally need an application to interface with the RevitPythonShell which I believe is how pyrevit works behind the scenes. Python is generally not used outside a program without a wrapper/shell to run it, or an IDE.

1 Like

After my first days with pyRevit I´m really excited. I got my first hook scripts running, my first pushbuttons and I´m looking forward to integrate the dynamo scripts. It´s a really amazing tool. Also good that there is a explanation for deploying it to a team, still have to figure out all the pro´s and con´s but i really hope i can setup this for our team :slight_smile:

Deployment is simple. Set up a directory on your server and place all your extensions and bundles there. Use the pyRevit options on each machine to point to that extension directory. pyRevit . Settings >Custom Extension Directories. That’s all there is to it.

You can continue to add new scripts to the directory on the fly. Each time a user loads Revit (or reloads pyRevit, they automatically get the new stuff.

I keep two extension directories. One for the office and one I use for development. When I get something to the point I want to deploy - I just copy it over to the office directory.

image

1 Like

Ok so that would pretty much be the same method we use now for “deploying” the dynamo scripts.

But now whats the advantage of this CLI method?

That is more appropriate for larger organizations and those deploying customized pyRevit installations. For instance, if you want to pre-configure all the pyRevit settings and want to deploy all the apps locally.

The disadvantage of a centralized location on the server for scripts is that you have to be connected to the server. This might not be possible if you are off-site.

For small and mid-size organizations with limit IT support, I find the central server setup the simplest. We just do pyRevit installs to the user’s local app dir to avoid any admin password needs.

For a larger organization with good IT support and those that need deployment to systems not always connected to the network, using the CLI for deployment might be a better option.

Thanks for the explanation aaron, i already was afraid this will be complicated, but for me the CLI method will not be necessary :slight_smile:

I wanted to create the exact tool to add prefix to the view names and track the changes. Currently we use the aforementioned app but have to pay yearly for site licensing and it adds up for many users. Not sure why that functionality is not built in the software but it seems like many need it. If anyone wants to share their progress that would be great. Otherwise it’s a good excuse to learn python.

Hello @Shaun_Peppers

This is an iUpdater that only gets triggered if the detail number of a viewport changes, or if a view is placed on a sheet.
First step in naming views automatically.

[video-to-gif output image]

from Autodesk.Revit.DB import IUpdater, UpdaterId, ElementId, UpdaterRegistry
from Autodesk.Revit.DB import Element, ElementCategoryFilter, BuiltInCategory, ChangePriority,Element, ElementId, ElementParameterFilter, ParameterValueProvider, FilterStringEquals, BuiltInParameter
from Autodesk.Revit.UI import TaskDialog
from System import Guid

# Define the SimpleUpdater
class SimpleUpdater(IUpdater):
    def Execute(self, data):
        print("Updater was triggered!")
    def GetUpdaterId(self):
        # Return the unique identifier for this updater
        return self.updater_id

    def GetUpdaterName(self):
        return 'SimpleUpdaterName'

    def GetAdditionalInformation(self):
        return 'A simple updater for testing purposes'

    def GetChangePriority(self):
        return ChangePriority.Annotations

    def Initialize(self):
        # This is where you can add trigger conditions for the updater
        pass

    def Uninitialize(self):
        pass

# Get the current document and application
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application

# Create an instance of the updater
updater = SimpleUpdater()

# Create a unique Guid for the updater
guid = Guid.NewGuid()

# Create an UpdaterId using the AddInId of the current application and the unique Guid
updater_id = UpdaterId(app.ActiveAddInId, guid)

# Set the identifier in the updater instance
updater.updater_id = updater_id

# Create a filter for views
viewport_filter = ElementCategoryFilter(BuiltInCategory.OST_Viewports)

# Get the ElementId for the VIEWPORT_DETAIL_NUMBER parameter
param_id = ElementId(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)

# Register the updater and add the trigger
if not UpdaterRegistry.IsUpdaterRegistered(updater_id, doc):
    UpdaterRegistry.RegisterUpdater(updater, doc)
    # Add trigger for the modification of the VIEWPORT_DETAIL_NUMBER parameter
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeParameter(param_id))
    # Add trigger for the addition of a new viewport to a sheet
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeElementAddition())
    TaskDialog.Show('Success', 'Updater has been registered and trigger has been set!')
else:
    TaskDialog.Show('Notice', 'Updater is already registered.')
2 Likes

And here the full code to auto name views :slight_smile:

[video-to-gif output image]

Still some issues to solve at registering the updater, use code for testing purposes only.

from Autodesk.Revit.DB import IUpdater, UpdaterId, ElementId, UpdaterRegistry
from Autodesk.Revit.DB import Element, ElementCategoryFilter, BuiltInCategory, ChangePriority,Element, ElementId, ElementParameterFilter, ParameterValueProvider, FilterStringEquals, BuiltInParameter
from Autodesk.Revit.UI import TaskDialog
from Autodesk.Revit.DB import Transaction
from System import Guid

# Define the SimpleUpdater
class SimpleUpdater(IUpdater):
    def __init__(self, doc):
        self._doc = doc
    def Execute(self, data):
        print("Updater was triggered!")
        added_ids = data.GetAddedElementIds()
        for id in added_ids:
            viewport = self._doc.GetElement(id)
            view_id = viewport.ViewId
            view = self._doc.GetElement(view_id)

            # Fetch parameters
            try:
                sheet = self._doc.GetElement(viewport.SheetId)  # Get the sheet containing the viewport
                from Autodesk.Revit.DB import BuiltInParameter
                sheet_number_param = sheet.get_Parameter(BuiltInParameter.SHEET_NUMBER)  # Fetch sheet number
                detail_number_param = viewport.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)  # Fetch detail number
                
                if sheet_number_param and detail_number_param:
                    sheet_number = sheet_number_param.AsString()
                    detail_number = detail_number_param.AsString()

                    # Construct the new view name
                    new_name = "{} - {} - {}".format(sheet_number, "Section", detail_number)

                    # Set the new name to the view
                    view.Name = new_name
                    print("New view name set to: " + new_name)

                else:
                    print("Failed to retrieve sheet or detail number for view: " + view.Name)

            except Exception as e:
                print("Error renaming the view: {}".format(e))
        
        # Handle modified elements
        modified_ids = data.GetModifiedElementIds()
        print("Number of modified element IDs: {}".format(len(modified_ids)))
        for id in modified_ids:
            viewport = self._doc.GetElement(id)
            view_id = viewport.ViewId
            view = self._doc.GetElement(view_id)

            # Fetch parameters
            try:
                sheet = self._doc.GetElement(viewport.SheetId)  # Get the sheet containing the viewport
                from Autodesk.Revit.DB import BuiltInParameter
                sheet_number_param = sheet.get_Parameter(BuiltInParameter.SHEET_NUMBER)  # Fetch sheet number
                detail_number_param = viewport.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)  # Fetch detail number
                
                if sheet_number_param and detail_number_param:
                    sheet_number = sheet_number_param.AsString()
                    detail_number = detail_number_param.AsString()

                    # Construct the new view name
                    new_name = "{} - {} - {}".format(sheet_number, "Section", detail_number)

                    # Set the new name to the view
                    view.Name = new_name
                    print("New view name set to: " + new_name)

                else:
                    print("Failed to retrieve sheet or detail number for view: " + view.Name)

            except Exception as e:
                print("Error renaming the view: {}".format(e))
    def GetUpdaterId(self):
        # Return the unique identifier for this updater
        return self.updater_id

    def GetUpdaterName(self):
        return 'SimpleUpdaterName'

    def GetAdditionalInformation(self):
        return 'A simple updater for testing purposes'

    def GetChangePriority(self):
        return ChangePriority.Annotations

    def Initialize(self):
        # This is where you can add trigger conditions for the updater
        pass

    def Uninitialize(self):
        pass

# Get the current document and application
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application

# Create an instance of the updater
updater = SimpleUpdater(doc)

# Create a unique Guid for the updater
guid = Guid.NewGuid()

# Create an UpdaterId using the AddInId of the current application and the unique Guid
updater_id = UpdaterId(app.ActiveAddInId, guid)

# Set the identifier in the updater instance
updater.updater_id = updater_id

# Create a filter for views
viewport_filter = ElementCategoryFilter(BuiltInCategory.OST_Viewports)

# Get the ElementId for the VIEWPORT_DETAIL_NUMBER parameter
param_id = ElementId(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)

# Register the updater and add the trigger
if not UpdaterRegistry.IsUpdaterRegistered(updater_id, doc):
    UpdaterRegistry.RegisterUpdater(updater, doc)
    # Add trigger for the modification of the VIEWPORT_DETAIL_NUMBER parameter
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeParameter(param_id))
    # Add trigger for the addition of a new viewport to a sheet
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeElementAddition())
    TaskDialog.Show('Success', 'Updater has been registered and trigger has been set!')
else:
    TaskDialog.Show('Notice', 'Updater is already registered.')
5 Likes

Added some methods:

  • Rename views by sheet number, view type, detail number and an additional shared parameter
  • If view name already exists in the project rename the view that is “blocking” the name
  • Register/unregister with taskdialog

Also tried to get it working in Dynamo, while the registration works properly i gave up because error reporting and debugging is impossible…

from Autodesk.Revit.DB import IUpdater, UpdaterId, ElementId, UpdaterRegistry
from Autodesk.Revit.DB import Element, ElementCategoryFilter, BuiltInCategory, ChangePriority,Element, ElementId, ElementParameterFilter, ParameterValueProvider, FilterStringEquals, BuiltInParameter
from Autodesk.Revit.UI import TaskDialog,TaskDialogCommonButtons,TaskDialogResult
from Autodesk.Revit.DB import Transaction
from System import Guid

# Define the SimpleUpdater
class SimpleUpdater(IUpdater):

    def __init__(self, doc):
        self._doc = doc

    def Execute(self, data):
        from Autodesk.Revit.DB import ViewType, FilteredElementCollector, BuiltInCategory, BuiltInParameter

        def find_unique_name(name, views):
            """Returns a unique name by appending an incrementing number."""
            counter = 1
            new_name = name + " - old"
            while any(view.Name == new_name for view in views):
                new_name = "{} - old({})".format(name, counter)
                counter += 1
            return new_name

        print("Updater was triggered!")
        
        # Combine both element lists
        element_ids = list(data.GetAddedElementIds()) + list(data.GetModifiedElementIds())
        
        # Fetch all views for the name check
        existing_views = FilteredElementCollector(self._doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
        
        for id in element_ids:
            viewport = self._doc.GetElement(id)
            view_id = viewport.ViewId
            view = self._doc.GetElement(view_id)
            view_type = view.ViewType  # Get view type

            # Fetch parameters
            try:
                sheet = self._doc.GetElement(viewport.SheetId)
                sheet_number_param = sheet.get_Parameter(BuiltInParameter.SHEET_NUMBER)
                detail_number_param = viewport.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)
                suffix_param = view.LookupParameter('View Name Suffix (AFRY)')
                if sheet_number_param and detail_number_param:
                    sheet_number = sheet_number_param.AsString()
                    detail_number = detail_number_param.AsString()
                    suffix_value = "" if not suffix_param else suffix_param.AsString()
                    
                    # Determine naming based on view type
                    if view_type == ViewType.Section:
                        type_name = "Section"
                    elif view_type == ViewType.FloorPlan:
                        type_name = "FloorPlan"
                    else:
                        type_name = view_type.ToString()  # Default to view type name
                    
                    # Construct the new view name
                    new_name = "{} - {} - {}{}".format(sheet_number, type_name, detail_number, " - {}".format(suffix_value) if suffix_value else "")

                    # Check if the new name is already in use
                    for existing_view in existing_views:
                        if existing_view.Name == new_name:
                            # Find a unique name for the blocking view
                            unique_name = find_unique_name(new_name, existing_views)
                            existing_view.Name = unique_name
                            print("Existing view renamed to: " + unique_name)
                            break

                    # Set the new name to the current view
                    view.Name = new_name
                    print("New view name set to: " + new_name)
                else:
                    print("Failed to retrieve sheet or detail number for view: " + view.Name)
            except Exception as e:
                print("Error renaming the view: {}".format(e))
                
    def GetUpdaterId(self):
        # Return the unique identifier for this updater
        return self.updater_id

    def GetUpdaterName(self):
        return 'SimpleUpdaterName'

    def GetAdditionalInformation(self):
        return 'A simple updater for testing purposes'

    def GetChangePriority(self):
        return ChangePriority.Annotations

    def Initialize(self):
        # This is where you can add trigger conditions for the updater
        pass

    def Uninitialize(self):
        pass

# Get the current document and application
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application

# Create an instance of the updater
updater = SimpleUpdater(doc)

# Create a unique Guid for the updater
guid_string = "676d4886-3106-4b53-b1ac-9183947d3cdf"
guid = Guid(guid_string)

# Create an UpdaterId using the AddInId of the current application and the unique Guid
updater_id = UpdaterId(app.ActiveAddInId, guid)

# Set the identifier in the updater instance
updater.updater_id = updater_id

# Create a filter for views
viewport_filter = ElementCategoryFilter(BuiltInCategory.OST_Viewports)

# Get the ElementId for the VIEWPORT_DETAIL_NUMBER parameter
param_id = ElementId(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)

# If the updater is already registered, ask the user if they want to unregister it
if UpdaterRegistry.IsUpdaterRegistered(updater_id, doc):
    dialog_result = TaskDialog.Show("Updater Options", 
                                    "The updater is already registered. Do you want to unregister it?", 
                                    TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No)

    # Depending on the user's choice, unregister the updater
    if dialog_result == TaskDialogResult.Yes:
        # User chose to unregister the updater
        UpdaterRegistry.UnregisterUpdater(updater_id, doc)
        TaskDialog.Show('Success', 'Updater has been unregistered!')

else:
    # If the updater is not registered, then register it
    UpdaterRegistry.RegisterUpdater(updater, doc, True)
    # Add trigger for the modification of the VIEWPORT_DETAIL_NUMBER parameter
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeParameter(param_id))
    # Add trigger for the addition of a new viewport to a sheet
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeElementAddition())
    TaskDialog.Show('Success', 'Updater has been registered and trigger has been set!')
3 Likes

@gerhard.p This is great work. So is the goal to make this a plug-in for other users to load. I look forward testing it. Perhaps someone will join the conversation (Autodesk???).

I use the code in pyRevit.

But I also got it working in Dynamo, here you have a dynamo version :slight_smile:

import clr

# Add references to RevitAPI and RevitServices
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
clr.AddReference('RevitAPIUI')

from Autodesk.Revit.DB import *
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from System import Guid
from Autodesk.Revit.UI import TaskDialog,TaskDialogCommonButtons,TaskDialogResult
doc = DocumentManager.Instance.CurrentDBDocument
app = DocumentManager.Instance.CurrentUIApplication.Application


class SimpleUpdater(IUpdater):
    def __init__(self, doc):
        self._doc = doc

    def Execute(self, data):
		element_ids = list(data.GetAddedElementIds()) + list(data.GetModifiedElementIds())
		for id in element_ids:
			viewport = self._doc.GetElement(id)
			view_id = viewport.ViewId
			view = self._doc.GetElement(view_id)
			view_type = view.ViewType  # Get view type

            # Fetch parameters
			try:
				sheet = self._doc.GetElement(viewport.SheetId)
				sheet_number_param = sheet.get_Parameter(BuiltInParameter.SHEET_NUMBER)
				detail_number_param = viewport.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)
				if sheet_number_param and detail_number_param:
					sheet_number = sheet_number_param.AsString()
					detail_number = detail_number_param.AsString()
					# Determine naming based on view type
					type_name = view_type.ToString()
					# Construct the new view name
					new_name = sheet_number+" - "+type_name+" - "+detail_number
					# Set the new name to the current view
					view.Name = new_name

			except:
				pass

    def GetUpdaterId(self):
        # Return the unique identifier for this updater
        return self.updater_id

    def GetUpdaterName(self):
        return 'SimpleUpdaterName'

    def GetAdditionalInformation(self):
        return 'A simple updater for testing purposes'

    def GetChangePriority(self):
        return ChangePriority.Annotations

    def Initialize(self):
        # This is where you can add trigger conditions for the updater
        pass

    def Uninitialize(self):
        pass
# ... [other methods of the SimpleUpdater class]

# Create an instance of the updater
updater = SimpleUpdater(doc)

# Create a unique Guid for the updater
guid_string = "676d4886-3106-4b53-b1ac-9183947d3cdf"
guid = Guid(guid_string)

# Create an UpdaterId using the AddInId of the current application and the unique Guid
updater_id = UpdaterId(app.ActiveAddInId, guid)

# Set the identifier in the updater instance
updater.updater_id = updater_id

# Create a filter for views
viewport_filter = ElementCategoryFilter(BuiltInCategory.OST_Viewports)

# Get the ElementId for the VIEWPORT_DETAIL_NUMBER parameter
param_id = ElementId(BuiltInParameter.VIEWPORT_DETAIL_NUMBER)

# If the updater is already registered, ask the user if they want to unregister it
if UpdaterRegistry.IsUpdaterRegistered(updater_id, doc):
    dialog_result = TaskDialog.Show("Updater Options", 
                                    "The updater is already registered. Do you want to unregister it?", 
                                    TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No)

    # Depending on the user's choice, unregister the updater
    if dialog_result == TaskDialogResult.Yes:
        # User chose to unregister the updater
        UpdaterRegistry.UnregisterUpdater(updater_id, doc)
        TaskDialog.Show('Success', 'Updater has been unregistered!')

else:
    # If the updater is not registered, then register it
    UpdaterRegistry.RegisterUpdater(updater, doc, True)
    # Add trigger for the modification of the VIEWPORT_DETAIL_NUMBER parameter
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeParameter(param_id))
    # Add trigger for the addition of a new viewport to a sheet
    UpdaterRegistry.AddTrigger(updater_id, viewport_filter, Element.GetChangeTypeElementAddition())
    TaskDialog.Show('Success', 'Updater has been registered and trigger has been set!')

1 Like

I am still trying to get the PyRevit version working. I am used to C++ so that is new to me. The Dynamo script is great, I understand it and it works great. I will keep learning PyRevit as it seems like the way I would like to go in the long run. Thanks for sharing!

1 Like