Shared Parameters - Create and add to Family in Python

Hello Dynamo Friends :slight_smile:

I want to manipulate shared parameters with python.
My goal is to create them and add them to a family while in the model, not in the family editor.

As i have never done something like this before i started by trying to edit a type parameter of a family while in the model, with success:

class FamilyOption(IFamilyLoadOptions):
	def OnFamilyFound(self,familyInUse,overwriteParameterValues):
		overwriteParameterValues = True
		return True
		
	def OnSharedFamilyFound(self, sharedFamily, familyInUse, FamilySource, overwriteParameterValues):
		overwriteParameterValues = True
		return True

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

#Preparing input from dynamo to revit
familySymbol = UnwrapElement(IN[0])
family = familySymbol.Family
status = []
outcome=[]

#Do some action in a Transaction
TransactionManager.Instance.EnsureInTransaction(doc)
#for symbId in family.GetFamilySymbolIds():
#symbol = doc.GetElement(symbId)
if not familySymbol.IsActive:
	familySymbol.Activate()
		
TransactionManager.Instance.TransactionTaskDone()
TransactionManager.Instance.ForceCloseTransaction()
if IN[4]:
	try:
		famDoc= doc.EditFamily(family)
		params = famDoc.FamilyManager.Parameters
		types = famDoc.FamilyManager.Types
		trans = Transaction(famDoc)
		trans.Start("test")
		for type in types:
			if type.Name == IN[1]:
				famDoc.FamilyManager.CurrentType = type
		for param in params:
			if param.Definition.Name == IN[2]:
				famDoc.FamilyManager.Set(param,IN[3])
			
		trans.Commit()
		famDoc.LoadFamily(doc,FamilyOption())
		famDoc.Close(False)
		status.append("Success")
	except:
		status.append("Failed")

OUT = status

Thanks to @SeanP for that code.

As it worked out to edit the type parameter of a family while in the model i tried my luck on adding a shared Parameter. But i get the error “FamilyManager can only be used in the Family Editor.”

class FamilyOption(IFamilyLoadOptions):
	def OnFamilyFound(self,familyInUse,overwriteParameterValues):
		overwriteParameterValues = True
		return True
		
	def OnSharedFamilyFound(self, sharedFamily, familyInUse, FamilySource, overwriteParameterValues):
		overwriteParameterValues = True
		return True

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

#Preparing input from dynamo to revit
familySymbol = UnwrapElement(IN[0])
family = familySymbol.Family
status = []
outcome=[]
definitions = UnwrapElement(IN[6])
bipgs = UnwrapElement(IN[5])
isInstance = True

#Do some action in a Transaction
TransactionManager.Instance.EnsureInTransaction(doc)
#for symbId in family.GetFamilySymbolIds():
#symbol = doc.GetElement(symbId)
if not familySymbol.IsActive:
	familySymbol.Activate()
		
TransactionManager.Instance.TransactionTaskDone()
TransactionManager.Instance.ForceCloseTransaction()

famDoc= doc.EditFamily(family)
params = famDoc.FamilyManager.Parameters
types = famDoc.FamilyManager.Types
trans = Transaction(famDoc)
trans.Start("test")	
new = doc.FamilyManager.AddParameter(definitions,bipgs,isInstance)
outcome.append(new)
trans.Commit()
famDoc.LoadFamily(doc,FamilyOption())
famDoc.Close(False)
status.append("Success")

OUT = status

So why can i set the parameter but can´t add one? Do I have to open the family in the background before i can add a parameter? Isn´t EditFamily() already opening the family?

Creating a shared parameter will be the next task. Does the parameter have to exist in the sharedparameter.txt file before, or can i create and add it only in the model?

Kind regards :slight_smile:

Make this change, you aren’t currently using the Family manager on the family document.

famDoc.FamilyManager

1 Like

Ok that works! :slight_smile:

So when I´m editing a family in python the document now has to be the family document, I see.
And i also have to use additional transactions while editing the family.

Things are getting clearer, thank you very much sean :slight_smile:

2 Likes

Creating a shared parameter will be tricky. Have to create a definition first.
Found a thread that will help and will try my luck tomorrow.

The ExternalDefinition object can be created by a definition Group object from a shared parameters file. External parameter definition must belong to a Group which is nothing more than a collection of shared parameter definitions. The following process should be followed to add a parameter to an element: Open the shared parameters file, via the Application.OpenSharedParameterFile() method. Access an existing or create a new group, via the DefinitionFile.Groups property. Access an existing or create a new external parameter definition, via the DefinitionGroup.Definitions property. Create a new Binding object with the categories to which the parameter will be bound using an InstanceBinding or a TypeBinding object. Finally add the binding and definition to the document using the Document.ParameterBindings object.

1 Like

Yes, that part will be a little more lengthy, but it’s not too hard!

However, it does look like that post was a couple years ago, so it may not work if you are in Revit 22 or newer with the parameter changes.

1 Like

And I´m stuck,

I use the code from archilab node Add Shared Parameter to Project.

image

And i`m getting this error for creating the group:

Line 81: _def = group.Definitions.Create(_paramName, _paramType, _visible)

There is this method for creating definitions with createOptions:

So will i need this method for creating my group?

Something like:

opt=...
group.Definitions.Create(opt)

Instead of

group.Definitions.Create(.,.,.)
_paramName = "TestSharedParameter"
_groupName = "Wanddurchbruch Gruppe"
_paramType = "Länge"
_visible = True
_category = BuiltInCategory.OST_Walls
_paramGroup = "PG_IDENTITY_DATA"
_instance = True

def ParamBindingExists(_doc, _paramName, _paramType):
	map = doc.ParameterBindings
	iterator = map.ForwardIterator()
	iterator.Reset()
	while iterator.MoveNext():
		if iterator.Key != None and iterator.Key.Name == _paramName and iterator.Key.ParameterType == _paramType:
			paramExists = True
			break
		else:
			paramExists = False
	return paramExists

def RemoveParamBinding(_doc, _paramName, _paramType):
	map = doc.ParameterBindings
	iterator = map.ForwardIterator()
	iterator.Reset()
	while iterator.MoveNext():
		if iterator.Key != None and iterator.Key.Name == _paramName and iterator.Key.ParameterType == _paramType:
			definition = iterator.Key
			break
	message = None
	if definition != None:
		map.Remove(definition)
		message = "Success"
	return message

def addParam(doc, _paramName, _visible, _instance, _groupName, _paramGroup):
	message = None
	if ParamBindingExists(doc, _paramName, _paramType):
		if not RemoveParamBinding(doc, _paramName, _paramType) == "Success":
			message = "Param Binding Not Removed Successfully"
		else:
			message = None
			
	group = file.Groups.get_Item(_groupName)

	if group == None:
		group = file.Groups.Create(_groupName)
	if group.Definitions.Contains(group.Definitions.Item[_paramName]):
		_def = group.Definitions.Item[_paramName]
	else:
		_def = group.Definitions.Create(_paramName, _paramType, _visible)
	
	param = doc.ParameterBindings.Insert(_def, bind, _paramGroup)
	return message
#"Start" the transaction
TransactionManager.Instance.EnsureInTransaction(doc)

try:
	file = app.OpenSharedParameterFile()
except:
	message = "No Shared Parameter file found."
	pass

#builtInCategory = System.Enum.ToObject(BuiltInCategory, _category.Id)
cats = app.Create.NewCategorySet()
cats.Insert(doc.Settings.Categories.get_Item(_category))
if _instance:
	bind = app.Create.NewInstanceBinding(cats)
else:
	bind = app.Create.NewTypeBinding(cats)


if isinstance(_paramName, list):
	for i, j, k in zip(_paramName, _visible, _instance):
		message = addParam(doc, i, j, k, _groupName, _paramGroup)
else:
	message = addParam(doc, _paramName, _visible, _instance, _groupName, _paramGroup)


# "End" the transaction
TransactionManager.Instance.TransactionTaskDone()

#Assign your output to the OUT variable
if message == None:
	OUT = "Success"
else:
	OUT = message

Ok, i have to define ExternalDefinitionCreateOptions for this method to work.

image

edit:

And the next problem is the Parameter Type, i don´t know how i can get the available types.
The Archilab node does not appear in dynamo if i want to add it to the graph.

I think if i can get the type the code would finally work

opt = ExternalDefinitionCreationOptions(_paramName, _paramType)

_def = group.Definitions.Create(opt)

The types retrieved from @Alban_de_Chasteigner ´s node do not work, seems like they are just strings:

I have to give up on this, no idea how i can get ParameterTypes as ForgeTypeID

For some reason i now get the same error on all my attempts.
Even the genius loci node gives me the same error, what does it mean?

Same error with ArchiLab:

Ok, here are a couple things to help.

I don’t have access to the graph right now, but here are images.

1 Like

Thanks for your help Sean, i got it working somehow :slight_smile:
I’m still pretty confused by all these methods that are necessary for that, ParameterBindings and so on…

The main problem i have is that i can run the code only a single time per revit session or i will get the error “InternalDefinition object has no attribute ParameterType”.
Deleting the Shared Parameters or closing dynamo does not help. Even closing the project does not help in most cases. I really have to close revit and open it again!

Any idea why this is happening?

Very good post and solutions, @gerhard.p
Could you post the code for the last solution?

Maybe the problem you are getting has to do with transaction malfunctioning.
Thank you

1 Like

Hello Diegas, welcome to the Dynamo Forum :slight_smile:

I did not post the full code until now because no one should see it before i clean it up :smiley:
I´m going to add it tomorror.

kind regards

2 Likes

Are you using CPython3 for your nodes? There are some issues with it that I have also seen related to this and other graphs. I am not completely sure, but I @c.poupin and other have addressed. For me I ported this to C# once I had the POC to avoid those issues.

1 Like

Hi,
here is a fix, (tested with Revit 2021, 2022, 2023)


#Inspired by an archilab node, by Konrad Sobon
#Modifications by Alban de Chasteigner 2020
# test fix by Cyril Poupin for Revit 2022+
#twitter : @geniusloci_bim
#geniusloci.bim@gmail.com
#https://github.com/albandechasteigner/GeniusLociForDynamo

import clr
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application
version=int(app.VersionNumber)

import System
from System.Collections.Generic import List
from System import Guid

_paramName = IN[0] if isinstance(IN[0],list) else [IN[0]]
_groupName = IN[1]
parameterTypes = IN[2] if isinstance(IN[2],list) else [IN[2]]
parameterGroups = IN[3] if isinstance(IN[3],list) else [IN[3]]
_instance = IN[4] if isinstance(IN[4],list) else [IN[4]]
_visible = IN[5] if isinstance(IN[5],list) else [IN[5]]
guids = IN[6] if isinstance(IN[6],list) else [IN[6]]
_categories = IN[7] if isinstance(IN[7],list) else [IN[7]]

paramTypes_len = len(parameterTypes) == 1
paramGroups_len = len(parameterGroups) == 1
instance_len = len(_instance) == 1
visible_len = len(_visible) == 1
guid_len = len(guids) == 1

if version < 2022:
	_paramGroup = [System.Enum.Parse(Autodesk.Revit.DB.BuiltInParameterGroup, parameterGroup) if isinstance(parameterGroup, basestring) else parameterGroup for parameterGroup in parameterGroups]
	_paramType = [System.Enum.Parse(Autodesk.Revit.DB.ParameterType, parameterType) if isinstance(parameterType, basestring) else parameterType for parameterType in parameterTypes]
else:
	# parameter Group 
	propGP_infos = clr.GetClrType(GroupTypeId).GetProperties()
	dictGrp_forgeTypeId = {p.Name : p.GetGetMethod().Invoke(None, None) for p in propGP_infos}
	_paramGroup = [dictGrp_forgeTypeId.get(parameterGroup) for parameterGroup in parameterGroups if isinstance(parameterGroup, basestring)]
	# compute a dictionnary of all SpecTypeId {SpecTypeId Name : SpecTypeId}
	dict_forgeTypeId = {}
	#
	for specTypeId_class in [clr.GetClrType(SpecTypeId), clr.GetClrType(SpecTypeId.Boolean), clr.GetClrType(SpecTypeId.Int), clr.GetClrType(SpecTypeId.String)]:
		prop_infos = specTypeId_class.GetProperties()
		dict_temp_forgeTypeId = {p.Name : p.GetGetMethod().Invoke(None, None) for p in prop_infos}
		dict_forgeTypeId = dict(dict_forgeTypeId, **dict_temp_forgeTypeId)
	#
	invalid_forgeTypeId = ForgeTypeId()
	_paramType=[dict_forgeTypeId.get(parameterType, invalid_forgeTypeId ) for parameterType in parameterTypes if isinstance(parameterType, basestring)]
	
_guid = [Guid(guid) if isinstance(guid, basestring) else guid for guid in guids]

def IsEqualTo_ParameterType(para_def, paraType):
	"""
	check if ParameterType of a defintion is Equal to a other ParameterType (or SpecTypeId)
	"""
	if version < 2022:
		return para_def.ParameterType == paraType
	else:
		return para_def.GetDataType() == paraType

def ParamBindingExists(_doc, _paramName, _paramType):
	map = doc.ParameterBindings
	iterator = map.ForwardIterator()
	iterator.Reset()
	while iterator.MoveNext():
		if iterator.Key != None and iterator.Key.Name == _paramName and IsEqualTo_ParameterType(iterator.Key, _paramType):
			paramExists = True
			break
		else:
			paramExists = False
	return paramExists

def RemoveParamBinding(_doc, _paramName, _paramType):
	map = doc.ParameterBindings
	iterator = map.ForwardIterator()
	iterator.Reset()
	while iterator.MoveNext():
		if iterator.Key != None and iterator.Key.Name == _paramName and IsEqualTo_ParameterType(iterator.Key, _paramType):
			definition = iterator.Key
			break
	message = None
	if definition != None:
		map.Remove(definition)
		message = "Success"
	return message

def addParam(doc, _paramName, _paramType, _visible, _instance, _guid, _groupName, _paramGroup,_categories):
	message = None
	if ParamBindingExists(doc, _paramName, _paramType):
		if not RemoveParamBinding(doc, _paramName, _paramType) == "Success":
			message = "Param Binding Not Removed Successfully"
		else:
			message = None		
	group = app.OpenSharedParameterFile().Groups.get_Item(_groupName)
	if group == None:
		group = file.Groups.Create(_groupName)
	if group.Definitions.Contains(group.Definitions.Item[_paramName]):
		_def = group.Definitions.Item[_paramName]
	else:
		opt = ExternalDefinitionCreationOptions(_paramName, _paramType)
		opt.Visible = _visible
		if _guid != None:
			opt.GUID = _guid 
		_def = group.Definitions.Create(opt)
	cats=app.Create.NewCategorySet()
	builtInCategories=[doc.Settings.Categories.get_Item(System.Enum.ToObject(BuiltInCategory, _category.Id)) for _category in _categories]
	[cats.Insert(builtInCategory) for builtInCategory in builtInCategories]
	if _instance:
		bind=app.Create.NewInstanceBinding(cats)
	else:
		bind=app.Create.NewTypeBinding(cats)
	param = doc.ParameterBindings.Insert(_def, bind, _paramGroup)
	return message
	
TransactionManager.Instance.EnsureInTransaction(doc)
try:
	file = app.OpenSharedParameterFile()
except:
	message = "No Shared Parameter file found."
	pass

for number in xrange(len(_paramName)):
	t = 0 if paramTypes_len else number
	g = 0 if paramGroups_len else number
	i = 0 if instance_len else number
	v = 0 if visible_len else number
	d = 0 if guid_len else number
	message = addParam(doc, _paramName[number], _paramType[t], _visible[v], _instance[i], _guid[d], _groupName, _paramGroup[g],_categories)
		
TransactionManager.Instance.TransactionTaskDone()

if message == None:
	OUT = "Success"
else:
	OUT = message

I could not test all the parameters, so feedbacks are welcome

FYI @Alban_de_Chasteigner :slightly_smiling_face:

6 Likes

Very nice !
Thanks Cyril for this ingenious improvement of the script.
With these changes, the custom node is ready for all versions of Revit (isn’t it Revit 2024?)

2 Likes

I didn’t try yet with 2024 :upside_down_face:

1 Like

I can´t get this working in revit 2023 :confused:

parameterGroups = [BuiltInParameterGroup.PG_IDENTITY_DATA]

    if version < 2023:
        _paramGroups = [System.Enum.Parse(Autodesk.Revit.DB.BuiltInParameterGroup, parameterGroup) if isinstance(parameterGroup, basestring) else parameterGroup for parameterGroup in parameterGroups]
        _paramTypes = [System.Enum.Parse(Autodesk.Revit.DB.ParameterType, parameterType) if isinstance(parameterType, basestring) else parameterType for parameterType in parameterTypes]
    else:
        # parameter Group 
        propGP_infos = clr.GetClrType(GroupTypeId).GetProperties()
        dictGrp_forgeTypeId = {p.Name : p.GetGetMethod().Invoke(None, None) for p in propGP_infos}
        _paramGroups = [dictGrp_forgeTypeId.get(parameterGroup) for parameterGroup in parameterGroups if isinstance(parameterGroup, basestring)]

dictGrp_forgeTypeId.get(parameterGroup) is just giving me ‘None’.

try this instead

parameterGroups = ["Data"]

2 Likes

That works :slight_smile:
So i need another group input for 22 and 23, and i just found out groups changed in general, the group “other”(invalid) doesn´t exist in 23 anymore.

Thank you ciril!

if version < 2023:
    parameterGroups = [BuiltInParameterGroup.PG_TEXT]
else:
    parameterGroups = ["Text"]
1 Like

It seems like the group INVALID also exists in Revit 2023

but it is not retrieved as a grouptypeId with this method “clr.GetClrType(GroupTypeId).GetProperties()”.

So I tried using an empty forgetypeid object as a parameter group to make a binding but with no luck.

“Exception: Input groupTypeId is not an identifier of a built-in parameter group.”

Any thougths on how to make this work for the INVALID group?
Any chance to get the member name?