Create Assembly in Python

Hello,
I am trying to create an assembly instance in Dynamo Python. I am using ironpython, but cpython3 would also be acceptable. Here is the code:

import System
import clr

# dynamo transaction
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI.Selection import *
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication




if IN[1]:  # boolean node
	# unwrap dynamo elements to revit elements
	el_L = UnwrapElement( IN[0] )
	
	# convert el_id python List to .NET list
	el_id_L = [el.Id     for el in el_L]
	el_id_NET_L = System.Collections.Generic.List[ElementId](el_id_L)
	
	
	# get catergory of the assembly instance
	cat = uiapp.ActiveUIDocument.Document.Settings.Categories.get_Item('Generic Models')
	
	# check if category can be used
	if (not AssemblyInstance.IsValidNamingCategory(doc, cat.Id, el_id_NET_L)):
		raise ValueError('"{}" category can not be used for the input els.'.format(cat.Name) )
	

	
	# first transaction - create assembly
	TransactionManager.Instance.EnsureInTransaction( doc )
	
	assembly = AssemblyInstance.Create(doc, el_id_NET_L, cat.Id)
	doc.Regenerate()
	
	TransactionManager.Instance.TransactionTaskDone()
	
	
	
	# second transaction - modify assembly name
	TransactionManager.Instance.EnsureInTransaction( doc )
	
	assembly.AssemblyTypeName = "Assembly Random Name"  # THIS LINE RAISES AN ERROR: 'Exception: No valid type for the assembly instance.'
	
	TransactionManager.Instance.TransactionTaskDone()

The code creates an assembly instance, but when it gets to the line: assembly.AssemblyTypeName = "Assembly Random Name" it raises an error: Exception: No valid type for the assembly instance.

Does anyone know how to set a custom name of an assembly instance?
I would be grateful for any kind of help.

Thank you in advance.
Attached are the .rvt and .dyn files.
assembly.rvt (3.0 MB)
create assembly.dyn (7.7 KB)

item=AssemblyItem
n=NewName
item.AssemblyTypeName=n #Set name of item as n

Use looping if you have multiple Items.

1 Like

Thank you for the reply @Hyunu_Kim ,
But I didn’t understand your reply. Are you suggestion the last lines from the upper code to look like this:

    # second transaction - modify assembly name
    TransactionManager.Instance.EnsureInTransaction( doc )
    
    item = assembly
    n = "Assembly Random Name"
    item.AssemblyTypeName = n  #Set name of item as n
    
    TransactionManager.Instance.TransactionTaskDone()

I just tried - it raises the same error: No valid type for the assembly instance.

Hi @george you need 2 separate transactions

import sys
import clr
import System
from System.Collections.Generic import List
# dynamo transaction
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# revit dlls
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI.Selection import *
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication


if IN[1]:  # boolean node
	# unwrap dynamo elements to revit elements
	el_L = UnwrapElement( IN[0] )
	TransactionManager.Instance.ForceCloseTransaction()
	# convert el_id python List to .NET list
	el_id_NET_L = List[ElementId]([el.Id  for el in el_L])
	bic = BuiltInCategory.OST_GenericModel
	bic_Id = ElementId(bic)
	# check if category can be used
	if (not AssemblyInstance.IsValidNamingCategory(doc, bic_Id, el_id_NET_L)):
		raise ValueError('"{}" category can not be used for the input els.'.format(DB.LabelUtils.GetLabelFor(bic)) )
	tg = DB.TransactionGroup(doc, "create assembly")
	tg.Start()
	#
	with DB.Transaction(doc) as t1:
		t1.Start("create assembly")
		assembly = AssemblyInstance.Create(doc, el_id_NET_L, bic_Id)
		t1.Commit()
	#
	with DB.Transaction(doc) as t2:
		t2.Start("change Name")
		assembly.AssemblyTypeName = "Assembly Random Name"
		t2.Commit()
	tg.Assimilate()
3 Likes

Hi @c.poupin ,
Thank you very much for posting the solution!

So the dynamo transactions I posted in my code are not 2 separate transactions? (I also tried with Revit AP transactions, and it didn’t work). You meant: I need to create a transaction group from two separate transactions?

Can you give me an advice, when I should make this transaction group? Is there some some general rule? And when it the group is not necessary, and I can just use two transactions.

Thank you once again for the shared knowledge.

I don’t know, Maybe an issue with the Dynamo Transaction Wrapper

1 Like

Hi @c.poupin ,
Thank you again for the reply. I tried replacing the Dynamo transactions with Revit API, and still got the same error. So it must be that fact that they have not been grouped, which resulted in the raised error.

Can you give me an advice, when I should make the transaction group?

transaction group is not a requirement here, works with 2 transactions

test transaction

1 Like

Thank you very much once again for the shared knowledge @c.poupin .
When I run the code the same way you showed (inside a python node), it works, without issues.

Interestingly, when I make a function from your code (def Assembly) and save the function in a dynamo.py file, and import the file into dynamo python node with:

import sys
sys.path.append('c:\revit\python')
import dynamo

the following error is raised:

Transaction group cannot be started during an active transaction.

But when I rerun the python node again, then it works.
So on the first run, it always raises the upper error, and the afterwards it works.

I don’t understand what is the issue. In that python node, I am literally only calling the dynamo.Assembly() function, nothing else. So there shouldn’t be any other transactions started.

The dynamo.py looks exactly as your code, I just replaced the if IN[1] with def Assembly, that’s all.

make sure you have added this to your python node (before calling your function)

TransactionManager.Instance.ForceCloseTransaction()

1 Like

Hi @c.poupin ,
It now works, even on the first run of the python! Thank you.

But if I click on ‘Undo’ button in Revit (to undo the creation of the assembly and remove it from the ‘Project Browser’) - and if run the Python node again (I add just # to trick the Python node into thinking that the code changed), the following error appears:

One or more element ids was not permitted for membership in the assembly instance. Elements should be of a valid category and should not be a member of an existing assembly.
Parameter name: assemblyMemberIds

The problem goes away if I follow your principle: of closing the .dyn file after each run of the Python node. And opening the .dyn file again and rerunning it.
I don’t really understand why this happens.

It’s probably due to the DataBinding close and re-open your dyn or use the dynamoplayer

see this post

1 Like

Thank you for the new explanation about this @c.poupin !

I consider your method on solving this issue, much better (faster):

  • ‘Run’ the 'dyn file to create an assembly via Python node
  • click on ‘Undo’ button in Revit, to remove the assembly
  • in Dynamo choose: ‘File->Open Recent Files’ and choose the top most (latest one). When asked if I want to save the current file: choose ‘No’.
  • ‘Run’ the .dyn file to create an assembly again for the same inputs

The method from Jason’s topic:

  • ‘Run’ the 'dyn file to create the assembly via Python node
  • click on ‘Undo’ button in Revit, to remove the assembly
  • Disconnect ‘Select Model Elements’ node from the python node
  • ‘Run’ the .dyn file
  • ‘Save’ the .dyn file
  • Connect the ‘Select Model Elements’ node to the python node
  • ‘Save’ the .dyn file
  • ‘Run’ the .dyn file to create an assembly again for the same inputs

The only part which confuses me from that topic is:

Custom nodes and python based nodes will not necessarily work this way.
For python nodes you’ll either need to revise how transactions and elements are handled in your code, or wrap it in a custom node.

Do you know what he meant with that?

Hi @george

we digress from the subject of this topic, please start a new topic this one is already solved

1 Like

hello I tried the code and it does not work, it indicates expected BuiltinparameterGroup, got BuiltinCategory. I am using Revit 2019 with Dynamo 2.0.4.

Hi,
try replace
raise ValueError('"{}" category can not be used for the input els.'.format(DB.LabelUtils.GetLabelFor(bic)) )

by

raise ValueError('"{}" category can not be used for the input element'.format(DB.Category.GetCategory(doc, bic).Name) )

2 Likes

Hello Guys

Very interesting discussion.

The code only works for GenericModels because of “bic = BuiltInCategory.OST_GenericModel”
with a small change “bic = el_L[0].Category.Id.IntegerValue” you can used it for different categories.

But if I want to assemble elements from different categories (let’s say one generic model and a wall) it won’t work.

Any suggestions on how to overcome this?

Cheers,
Nery

If not mistaken, the way assemblies are created, if you input two different elements, you will get two different assemblies. So I would look into incorporating a second selection method and use the Assembly.AddMembers node from clockwork.

If you have additional questions, please start a new thread and reference this thread in your post. That way will warrant more input from others.