Get copied elements from Revit function Copy

Hi there,

I have a Python Script node where I use the Copy function from Revit to copy/paste elements in my project.

What I would like to get is the copied elements in my script. But I have some issues.

Here is the code :

import clr
import time

clr.AddReference("RevitAPIUI")
from Autodesk.Revit.UI import RevitCommandId

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
uiapp = DocumentManager.Instance.CurrentUIApplication

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

# Get all elements in the document before the copy operation
collector = FilteredElementCollector(doc)
existing_elements = set(collector.WhereElementIsNotElementType().ToElementIds())

# Start a transaction
TransactionManager.Instance.EnsureInTransaction(doc)

# Perform the copy operation
CmndID = RevitCommandId.LookupCommandId("ID_EDIT_MOVE_COPY")
uiapp.PostCommand(CmndID)

# Commit the transaction
TransactionManager.Instance.TransactionTaskDone()

# Wait for the command to complete
time.sleep(1)  # Adjust the sleep time as necessary

# Get all elements in the document after the copy operation
new_collector = FilteredElementCollector(doc)
new_elements = set(new_collector.WhereElementIsNotElementType().ToElementIds())

# Determine the copied elements by finding the difference
copied_elements = new_elements - existing_elements

# Convert the copied elements to Dynamo types and output them
output = [doc.GetElement(id).ToDSType(True) for id in copied_elements]

# Return the copied elements
OUT = output

The thing with this code, is that the new_elements list is exactly the same as the existing_elements, even with the classes TransactionManager and Time.

Is is possible, without launching the code twice, to have two different lists so the copied_elements list is not empty ?

Thank you.

Dynamo wraps itself in a transaction to be executed in Revit. This essentially means that everything you do in Dynamo happens “all at once” and then gets committed to Revit.

Your python transaction is nested inside this Dynamo transaction, so the list of new elements is returned before the Dynamo transaction finishes and the changes have actually been committed to the Revit model. You need to introduce a new transaction at the Dynamo level so that Dynamo executes your logic in two stages - once when running the command and once after. All you should need to do is split your python into two nodes and add a Transaction node in between (Python: execute command > Dynamo: commit transaction > Python: get new elements).

Thanks for your reply @Nick_Boyts.

I’ve tried several things, but nothing worked. Both lists have the same amount of elements.

Can you tell me what is wrong with my nodes ?

First node :

# Get all elements in the document before the copy operation
collector = FilteredElementCollector(doc)
existing_elements = set(collector.WhereElementIsNotElementType().ToElementIds())

CmndID = RevitCommandId.LookupCommandId("ID_EDIT_MOVE_COPY")
uiapp.PostCommand(CmndID)

OUT = existing_elements

Second node :

TransactionManager.Instance.ForceCloseTransaction()
TransactionManager.Instance.EnsureInTransaction(doc)
TransactionManager.Instance.TransactionTaskDone()

Second node (other method) :

t = Transaction(doc, 'CopyElements')
t.Start()
t.Commit()

Third node :

# Get all elements in the document after the copy operation
new_collector = FilteredElementCollector(doc)
new_elements = set(new_collector.WhereElementIsNotElementType().ToElementIds())

# Return the copied elements
OUT = new_elements

All of my nodes contain this at the beginning :

import clr
import sys
import time
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')
import System
from System import Array
from System.Collections.Generic import *
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager 
from RevitServices.Transactions import TransactionManager 

clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk 
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication 
app = uiapp.Application 
uidoc = uiapp.ActiveUIDocument

Capture d’écran 2024-07-16 061932

Use a Dynamo transaction node (either Start or Stop will work). You have the same issue whenever the transaction is in python.

Same probleme, unfortunatelly :frowning:

Capture d’écran 2024-07-16 155618
Capture d’écran 2024-07-16 155710
Capture d’écran 2024-07-16 155851

Hmm. It looks like Revit won’t let you “interrupt” a transaction with a postable command, even if that transaction has finished.

What’s the reasoning for using a postable command instead of an alternative that could be automated? Does the copied placement have a known location?

EDIT: More information on what might be happening. It sounds like postable commands don’t finish until after execution anyway, regardless of subtransactions.

2 Likes

When you use the Copy function in Revit in a Collaboration Model with Worksets, the elements that are copied are stored to the active workset.

I would like to create a script that retrieves the workset(s) stored in the selected elements before the copy and once this is done, the script associate the right workset(s) to the copied elements (like AutoCAD does with the Copy function with the blocks and layers).

I’ve managed to retrieve the worksets on selected elements but not the final part on the script (the Copy function to get the copied elements).

So you want to copy a set of elements while retaining the original associated workset. Are you also moving the copied elements or do they retain the same location as well?

Yes, like copy a whole level or room to another level or room, respectively.

Does the workset exist in both models?

If so try this:

  1. Get the elements from the source document
  2. Get the workset of each element in the source document
  3. Start a transaction in the target model
  4. Copy the elements to the target model
  5. Commit the transaction in the target model
  6. Start a second transaction in the target model
  7. Set the workset in the target model
  8. Commit the second transaction in the target model
2 Likes

It’s on the same model @jacob.small.

Sorry if it was misunderstood.

But when you copy an element in the same model somewhere else in the proejct, the active workset is stored in the element and is mostly the time different from the workset that is actually stored in the existing element.

Ok - swap ‘source document’ for ‘active document’ and ‘target document’ for ‘active document’ in my prior instructions. Rest of the process should stay the same. :slight_smile:

It seems to be the solution of what I’m looking for.

I’ll try this when I can and keep you posted.

Thank you :+1:

EDIT : I do not have the knowledge and the time to adapt my code on this way, unfortunatelly.
But, I’ve found another way to do this and it seems fine to me.

It was the plan B, if the first plan was a dead end.

Here is a preview of the result :

ezgif-6-d078843e68

And here is the code :

Now I need to optimize the code to avoid some errors (pass the annotations for example).

Thank you everyone, I really appreciate your help !

1 Like