Plan header to view name -> script too slow

Hello dynamiters,
I wrote a code that transport certain parameters in the plan header to the name of the view.
It does work but way too slow, it takes for a big project around 5 minutes! I would like to understand why it is that slow and maybe a way to optizime the code.

#!/usr/bin/python
#-*- coding:utf-8 -*-

import clr
clr.AddReference('RevitAPI')
clr.AddReference("RevitServices")
clr.AddReference("RevitNodes")
from Revit.Elements import *
import RevitServices
import Revit
import Autodesk
from Autodesk.Revit.DB import *

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument


def param_blocktitel_view(toggle, cat, b_gewerk):
    if toggle == True:

        collector = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Sheets).OfClass(ViewSheet)

        
        #Get views from Architecture 
        views = [view for view in collector if view.LookupParameter("5d Planbrowserstruktur Stufe 1").AsString() == b_gewerk]

        #Get Plankopf of the view and its name
        plankopfLst = []
        type_name_list = []
        plan_name_list = []
        architecture_views = []
        same_names = []
        new_names = []
        for i in xrange(len(views)):
            if cat is not None:
                fec = FilteredElementCollector(doc, views[i].Id).WhereElementIsNotElementType().OfCategoryId(cat.Id).GetElementIterator()
            else:
                fec = FilteredElementCollector(doc, views[i].Id).WhereElementIsNotElementType().GetElementIterator()
            fec.Reset()
            view_el = []
            while fec.MoveNext():
                view_el.append(fec.Current)

            try:
                plankopf = view_el[0]
                plankopfLst.append(plankopf)
                name = plankopf.Symbol.Family.Name
                type_name_list.append(name)
                architecture_views.append(views[i])
            except:
                pass

        TransactionManager.Instance.EnsureInTransaction(doc)

        for view, ele, plan_family_name in zip(architecture_views, plankopfLst, type_name_list):
            if "Detailplanung" in plan_family_name:
				pass
            else:
                phase = view.LookupParameter("Phase").AsString()
                if phase != None:
                    gewerk = view.LookupParameter("Gewerk").AsString()
                    plantyp = view.LookupParameter("Plantyp").AsString()
                    bauteil = view.LookupParameter("Bauteil").AsString()
                    ebene = view.LookupParameter("Ebene").AsString()
                    nummer = view.LookupParameter("Nummer").AsString()

                    if (phase or gewerk or plantyp or bauteil or ebene or nummer) != None:
                        planName = phase + "_" + gewerk + "_" + plantyp + "_" + bauteil + "_" + ebene + "_" + nummer
                        plan_name_list.append(planName)

                        plannummer_param = ele.LookupParameter("Plannummer")
                        plannummer = plannummer_param.AsString()
                        if str(plannummer) == planName:
                            same_names.append( str(plannummer) + "/" + planName)
                        else:
                            
                            plannummer_param.Set(planName)
                            new_names.append(planName)

        TransactionManager.Instance.TransactionTaskDone()
        
        return [same_names, new_names]

     
#### life Code #####
bool = IN[0]
cat = UnwrapElement(IN[1])
bool_gewerk = IN[2]

out = param_blocktitel_view(bool, cat, bool_gewerk)
	
OUT = out

And the Dynamo script looks like that

Here are a few reasons:

  1. Using try except rather than handling your exceptions destroys performance. If an exception hits the performance penalty is huge - remove it and handle your exceptions!
  2. Instantiating hundreds of FilteredElementCollectors will hammer performance - refactor them out; if possible try removing them from your loop (I don’t know what you are doing so you’ll need to work that out).
  3. Creating large memory footprints with an over-use of lists, and lists of lists etc. Take this for example: for view, ele, plan_family_name in zip(architecture_views, plankopfLst, type_name_list): this is a clear case where you could declare your own class with a prop storing the view, ele and plan name, simplify the data structure (could store them all in a 1D list). Plus, you would be populating this list in the step before rather than having to create a new list for the sake of your for loop, and you would have the added benefit of a much clearer and coherent program structure.
  4. Lots of conditionals: or or or etc. Again, refactor to simplify your evaluation. Ifs can be refactored out or reduced using polymorphism and good OOP.

Other reasons:

  1. You’re using Dynamo and Revit, neither are performant.
  2. You’re running a script inside an app which runs inside another app.
  3. Python is interpreted before its compiled which is slow compared to say, C#.
5 Likes

Hey,

First of all, Thomas gave some really good reasons why your script is slow and pointers on how to refactor your script.

Correct me if I´m wrong, but you´re basically getting the titleblocks (planköpfe) and their sheets, then you take some of the sheets variables to create a new sheet number and assign it.

You could use the ElementParameterFilter to filter a titleblock collector, then another filter on the related sheets to check if they meet your requirements.

This can look like the following:

value_provider = ParameterValueProvider(ElementId(BuiltInParameter.SYMBOL_NAME_PARAM))
case_sensitive = False
evaluator = FilterStringContains()
filter_rule = FilterStringRule(value_provider, evaluator, "Detailplanung", case_sensitive)
inverted = True
parameter_filter = ElementParameterFilter(filter_rule, inverted)
titleblocks = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_TitleBlocks).WhereElementIsNotElementType().WherePasses(parameter_filter).ToElements()

Now to get the sheet of a titleblock you can use its OwnerViewId property and then doc.GetElement() do get the actual sheet instead of creating a new collector.

If you create another paarameter_filter as above, you can check if your sheet meets your requirement of "5d Planbrowserstruktur Stufe 1" == b_gewerk. Lastly you can set your new sheet number with sheet.SheetNumber = "my_new_number"

I would break each task into a function (better yet, collect them in a class) to make it more readable and for you to make changes piece by piece.

The building coder has a lot of examples on how to collect elements with different filters.

That being said, have you tried to build it up with Dynamo nodes? Was it faster? It might help you to see each step you´re doing.

4 Likes

Well, I found out where was the issue.

the collector is way to slow, i think it is a bug from revit, cause it doesnt make any sense to take so long to collect elements on the sheet. Here the problematic collector:
firstTTBL = FilteredElementCollector(doc,sheet.Id).OfCategory(BuiltInCategory.OST_TitleBlocks).FirstElement()

So What I did was the other way around, I collect first the titleblocks and then the property Element.OwnerViewId to obtain the sheets they belong to. This is surprisingly waaay faster. Here the code:

#-*- coding:utf-8 -*-

import clr
clr.AddReference('RevitAPI')
clr.AddReference("RevitServices")
clr.AddReference("RevitNodes")
from Revit.Elements import *
import RevitServices
import Revit
import Autodesk
from Autodesk.Revit.DB import *

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
 
def GetOwnerView(item):
	if hasattr(item, "OwnerViewId"): return item.Document.GetElement(item.OwnerViewId)
	else: return None

toggle = IN[0]
cat = UnwrapElement(IN[1])
bool_gewerk = IN[2]

same_names = []
new_names = []

if toggle:
    collector = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_TitleBlocks).ToElements()

    sheets = [GetOwnerView(x) for x in collector]

    tittleblock_list = []
    sheet_list = []
    for titleblock, sheet in zip(collector, sheets):
        try:
            if sheet.LookupParameter("5d Planbrowserstruktur Stufe 1").AsString() == bool_gewerk:
                tittleblock_list.append(titleblock)
                sheet_list.append(sheet)
        except:
            pass
    titleblock_name_list = [x.Symbol.Family.Name for x in tittleblock_list]

    TransactionManager.Instance.EnsureInTransaction(doc)

    for sheet, titleblock, titleblock_name in zip(sheet_list, tittleblock_list, titleblock_name_list):
        if "Detailplanung" in titleblock_name:
            pass
        else:
            phase = sheet.LookupParameter("Phase").AsString()
            if phase != None:
                gewerk = sheet.LookupParameter("Gewerk").AsString()
                plantyp = sheet.LookupParameter("Plantyp").AsString()
                bauteil = sheet.LookupParameter("Bauteil").AsString()
                ebene = sheet.LookupParameter("Ebene").AsString()
                nummer = sheet.LookupParameter("Nummer").AsString()

                if (phase or gewerk or plantyp or bauteil or ebene or nummer) != None:
                    planName = phase + "_" + gewerk + "_" + plantyp + "_" + bauteil + "_" + ebene + "_" + nummer

                    plannummer_param = titleblock.LookupParameter("Plannummer")
                    plannummer = plannummer_param.AsString()
                    if str(plannummer) == planName:
                        same_names.append( str(plannummer) + "/" + planName)
                    else:
                        plannummer_param.Set(planName)
                        new_names.append(planName)

    TransactionManager.Instance.TransactionTaskDone()
	
OUT = new_names, same_names
3 Likes