Copy Elements from View to View: Revit 2022

Hi All,
I’m working on a way to automate the creation of a new “Scenario” in Revit.
It goes like this:

  • Create new area scheme
  • Create area plans and copy over boundaries to new scheme
  • Copy areas from Existing (also known as Rentable) to new scheme

So far, I have been able to do the first two tasks, but get an error copying over the Areas:
errormsg

As far as I can tell my arguments are correct, according to the API the <class ‘list’> is a list of Element Ids, of course.

Looking at previous posts here and here, it appears my syntax is correct and it has all the references.

I tried both the Spring and RIE nodes, but after various attempts at different list configurations either got nothing or duplicate areas on the source view. Since they have not been updated to CPython/Revit 2022, maybe that’s why? If there are any up-to-date packages out there, I am open to that, but if not, it’s Python or bust.

Except for copying areas, you should be able to create an areascheme (called “Whatever”), create areaviewplans for the new scheme and copy over area boundaries from a model (if your Rentable area scheme is named “Existing”).

Here is the code. Props to @jacob.small and @c.poupin for their snippets!

#Python CopyAreasToNewScenario
import sys
import clr
clr.AddReference("System")
from System.Collections.Generic import List
from itertools import compress, repeat, groupby
from operator import itemgetter

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)

import Revit.Elements as RE
#import module for the Document and transactions
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

#import Revit API
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference("RevitAPIUI")
from Autodesk.Revit.UI import *
 
import System
from System.Collections.Generic import *

#get the current document in Revit.
doc = DocumentManager.Instance.CurrentDBDocument

run = IN[0] #input for the run control
if not run: sys.exit("\r\r\t\tSet Run to True\r\r") #a toggle to run the code or not

levels = []

all_Levels = FilteredElementCollector(doc) \
        .OfCategory(BuiltInCategory.OST_Levels) \
        .WhereElementIsNotElementType() \
        .ToElements()
        
level_names = [l.Name for l in all_Levels]


#Create existing Areaplan view dictionary
all_views = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()


views = []
for v in all_views:
    if not v.IsTemplate:
        if (doc.GetElement(v.GetTypeId())).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString() == "Existing":
            if not v.Name == "R001":
                if not v.Name == "S001":
                    views.append(v)

viewNames = []
for view in views:
       viewNames.append(view.Name)

viewDict = {viewNames[i]: views[i] for i in range(len(viewNames))}

#create new areascheme dictionary
newViews = []
for v in all_views:
    if not v.IsTemplate:
        if (doc.GetElement(v.GetTypeId())).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString() == "Whatever":
            if not v.Name == "R001":
                if not v.Name == "S001":
                    newViews.append(v)                 

newViewDict = {viewNames[j]: newViews[j] for j in range(len(viewNames))}

               
#collect all Area Schemes in the project
schemes = FilteredElementCollector(doc).OfClass(AreaScheme).WhereElementIsNotElementType().ToElements()

schemelst = []

#Get Existing areascheme
for s in schemes:
    if s.Name == 'Existing':
        exScheme = s
        exSchemeNm = exScheme.Name
        exSchemeId = exScheme.Id
  
pvp = ParameterValueProvider(ElementId(BuiltInParameter.AREA_SCHEME_ID))
fne = FilterNumericEquals()
ruleValue = exSchemeId
fRule = FilterElementIdRule(pvp, fne, ruleValue)
filter = ElementParameterFilter(fRule)

#Filter Areas based on Existing scheme
exstAreas = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Areas) \
           .WherePasses(filter).WhereElementIsNotElementType().ToElements()
            
areaLvlNames = []
areaIds = List[ElementId]()
areaLevelsIds = []
for area in exstAreas:
    areaId = area.Id
    areaLvlName = area.Level.Name
    areaLvlNames.append(areaLvlName)
    areaIds.Add(area.Id)

gpLevelNames = [list(i) for j, i in groupby(areaLvlNames)]

areaLevelsNms = zip(areaIds, areaLvlNames)
    
areaGroups = groupby(areaLevelsNms, itemgetter(1))    
areasByLvl = [[item[0] for item in data] for (key, data) in areaGroups]

#This zipped list is used for the CopyElements() arguments
levIds = zip(gpLevelNames, areasByLvl)


TransactionManager.Instance.EnsureInTransaction(doc)

newElems = []
for lnm, a in levIds:
    l = lnm[0]
    newElem = ElementTransformUtils.CopyElements(viewDict[l], a, newViewDict[l], None, None)
    newElems.append(newElem)

TransactionManager.Instance.TransactionTaskDone()

OUT = newElems

ScenarioSetup_test.dyn (18.8 KB)

Any insight is appreciated!
Thanks,
@lorhal

1 Like

I know it indicates you can provide a null reference, but see what happens if you provide a Transform object and a Copy Paste Options object into the CopyElements method.

If that doesn’t work, you’ll need to convert your Element ID list (currently your a variable to an iCollection. Line 78 of the code here has an example: dynamoPython/HideUnhideLinkLevelsGrids.py at 883c11fd62c3a2b35790a5e3fed87720bcd6e557 · Amoursol/dynamoPython · GitHub, note the import on lines 19 and 20 as well.

If that also doesn’t work, can you post a Revit model? I’m lazy and don’t like having to build the Revit model before I can try to build code.

1 Like

DummyBldgTest2022.rvt (4.0 MB)
Hi @jacob.small ,
OK I will try your suggestions. In the meantime, here is a dummy model (2022) for your use. This gave me the same error as my working model. In future posts, this may come in handy as well. I’m still in the process of getting our workflows figured out.
Stay tuned!
Thanks

this is due to a CPython3/PythonNet2.5.x issue(overload).
In addition to this problem, according to API doc Transform and CopyPasteOptions can be null which probably makes overload resolution more complicated

Hi @c.poupin,

I’m not clear on how CPython/IronPython overload factors into this. My understanding of overload is having methods with the same name and/or different types or numbers or parameters.

The CopyElements Method has three types, but I am only calling one of them.
Do I need to declare that specific one beforehand?

How does the difference between CPython and IronPython come into play?
I have had issues transitioning to CPython, but I can’t point to any specific reason.

After adding the Transform and Options to the code, it recognizes them, but I still get an error:

Can we rule out anything? I’ll try @jacob.small 's 2nd suggestion now.

  • IronPython Engine → IronPython (more detail here )
    IronPython is a .NET native Implementation of Python for .NET Framework that is built on top of the Dynamic Language Runtime.
  • CPython3 Engine → Python.NET (more detail here )
    Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR)

Exactly, PythonNet 2.5.x has trouble choosing the right method (overload resolution has been improved in the new version 3.x, but I have no idea if it works when the API allows passing null values ​​as arguments in the new version :thinking:)
IronPython does not have this problem

Appreciate the clarification, and I may look into getting PythonNet 3.x (can I do that?)

But, things have taken a left turn:

I went with @jacob.small’s List[ElementId] suggestion and the error went away, but all the copied Areas ended up on the “Existing” AreaPlan as Redundant Areas. A “P” replaced the first character of the Area Number. I assume that’s a Revit thing.

This is doubly strange because my tests showed the type to be ElementId before and after converting.

I reverted the CopyElements code to None on the Transform and Options args, so I think we can rule out overload.

If you have any ideas on why all the copied areas ended up on one level, I’m all ears.

Thanks,
@lorhal

ha ok, I thought you had already tested @jacob.small 's fix, it’s good to know that it works with null arguments

Correct. But your overloads were never right, as the input expects an ICollection of element IDs, which is not the same as a python List of element ids.

Python lists are a part of Python, and can contain anything.
ICollections are a part of .net, and have a strong type for the data they store (in this case element IDs) to ensure no bad data gets though.

So while you’re passing the right data, you’re doing so using the wrong container. You can sort of think of it like if ordering a bowl of soup. You want a bowl, but if you got ziplock bags containing soup instead, you’d probably be a little confused on how to deal with it. This was Revit’s way of expressing that confusion.

As far as ‘why are they on the wrong level, or in the wrong area plan, I’m not sure. My preference when duplicating areas and boundaries is to use the NewArea method, and the NewAreaBoundaryLine method respectively.

That said, it could be a transaction thing, where because the content hasn’t been committed all the way yet the level is assumed to be the first level found… Try breaking each of the creation aspects into it’s own transaction, and confirm if the issue persists.

Ah, wrong container. That makes sense. This Python vs .NET is throwing me because…I’m writing Python!

My graph does use NewAreaBoundaryLine (in the middle Python node) and
I have had success with the New Area / Line methods. I thought I would try CopyElements because there are a lot of shared parameters that need to come over.
And it seems way faster.

Since I got past the initial error, I’ll keep working on it.
Thanks for helping get this far!

1 Like

Well, I did some more tests and split up my data to one level and set of Areas, but got the same result of copying to the source View instead of the new one. I even put back the Transform and Options objects back, but same deal.

Re: CopyPasteOptions: What are the options? I don’t see any.

I do see a SetDuplicateTypeNamesHandler Method. Do you know anything about that? It’s the only thing that stands out to me that could affect the copying. But, it sound like it would cancel the operation instead of the behavior I am seeing.

At any rate, the CopyElements method would be extremely handy for my project. Have you ever seen it work, either in Python or C#?

Here is a stripped down graph that I used to try to get the CopyElements method to run.
It did not give me an error, but once again placed the copied areas in the source view.

Although technically this post has a solution due to the initial error being addressed, the actual goal was not reached: The method to actually work as advertised.

I may repost to the Revit API forum to get some clarity on:

  • What are the Options in CopyPasteOptions()?
  • Does the overloading issue have anything to do with the method’s behavior?
  • Has anybody actually had any success in using this method?
  • Could this be a C#-only method?

Thanks again for the help and info.

Best,
Loren

CopyElementsTest.dyn (11.3 KB)

I think I figured out why the CopyElements method does not work in my case:

I wanted to copy from one AreaPlan to another. Unfortunately, that means changing the Area Scheme parameter, which is read-only :slightly_frowning_face:.

Since the scheme did not change, the Areas ended up on the same view.

Oh well. It would be a nice addition to the API to be able to change it, no?

1 Like