FilterRule.ByRuleType input issues

I’m having two issues with the FilterRule.ByRuleType node.

First I can’t seem to get a Yes value into the node. If I use true it says it called with bool. If I use 1 it says it was called with int.

Second it doesn’t like what I’m using for the parameter. I can’t figure out a way to get a shared parameter as simply Parameter argument type.

1 Like

The required input is a Parameter, not a ParameterElement. The ParameterElement’s corresponding Parameter can be accessed directly through the API.

EDIT: My answer is actually partially incorrect. You do have to provide a Parameter, but the Parameter property of the ParameterElement is just a means of accessing a specific Parameter of the ParameterElement (as it is technically an Element itself).

It is easy to go from a Parameter to a ParameterElement, but not the other way around. As shown here, both the Parameter and ParameterElement share a common ElementId:

However, I’m not sure how to go from a Revit-native Parameter to one that can be used in Dynamo. What’s more confusing is that the Revit API method to create an equals rule has an ElementId of the parameter as one of its arguments whereas the Dynamo node instead uses the Parameter.

2 Likes

Thanks, I think I’m going to have to learn how to understand that stuff better. I’ve tried a bunch of things but I think I know so little that I can’t even get it wrong in a reasonable way. Sounds like there are no node so I’m going to have to wait to learn to code.

1 Like

Thanks for the update. So I wanted to try to get the parameter directly because I’m running this on new projects without elements placed that would use the shared parameter I’m trying to create a filter rule for. After this thread I think it might be easier to place an element and use it to get the parameter and then delete it afterwards. I think getting the parameter from an element will give me the right argument type. It just seems like a waste to me. in the mean time I’m going to study C# and python so I can translate the API instead of just randomly smashing words together.

1 Like

Yes, in that case it would be easiest to use Parameter.ParameterByName to get the parameter instead.

1 Like

Thanks for the help. I’ve been able to get the parameter using a element I’m placing and then deleting.

I still for some reason can’t get a Yes value to actually work here. I’ve tried every combination of string and select rule pull down nodes along with true, “Yes”, and 1 to try to get it to work. It either return a null result or it creates a rule expect it doesn’t actually set the value when I check it in Revit.

I’m I doing anything wrong here. It seems like 1 should work.

1 Like

There is most likely an issue with the Dynamo implementation of creating FilterRules as providing an integer for a YesNo parameter is completely valid.

Here’s a starting point for creating an FilterIntegerRule and a corresponding ParameterFilterElement:

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

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

from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument

param_id = ElementId(IN[0])
# Get ParameterElement from Parameter's ElementId
param_elem = doc.GetElement(param_id)
definition = param_elem.GetDefinition()
binding_map = doc.ParameterBindings
binding = binding_map.Item[definition]
category_set = binding.Categories.GetEnumerator()

cat_ids = [cat.Id for cat in category_set]
# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_ids)
cat_ids_valid = ParameterFilterUtilities.RemoveUnfilterableCategories(cat_ids_list)

rule = ParameterFilterRuleFactory.CreateEqualsRule(param_id, 1)
# Convert single FitlerRule to List<FilterRule>
rules = List[FilterRule]([rule])

TransactionManager.Instance.EnsureInTransaction(doc)
pfe = ParameterFilterElement.Create(doc, 'Yes No Test', cat_ids_valid, rules)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfe, pfe.Id

YesNo.dyn (8.9 KB)

Here’s a more elaborate explanation on what’s going on:

Add references to the Revit API and import everything from the Autodesk.Revit.DB namespace

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

Add a reference to RevitServices from a Dynamo DLL and import the DocumentManager (to access the current Revit document) and the TransactionManager (to allow us to modify the document)

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

Import List from the System.Collections.Generic namespace so that we can use native .NET Lists when required.

from System.Collections.Generic import List

Given the parameter’s Id, as an integer, cast it to an ElementId and get the corresponding ParameterElement from the current document.

doc = DocumentManager.Instance.CurrentDBDocument
param_id = ElementId(IN[0])
param_elem = doc.GetElement(param_id)

Get the Parameter’s Definition from the ParameterElement. Get the document’s BindingMap which relates all of the Parameters to their Definitions. Then get the binding of the specific Parameter we have provided.

definition = param_elem.GetDefinition()
binding_map = doc.ParameterBindings
binding = binding_map.Item[definition]

Get the parameter’s categories from the binding we just retrieved.

category_set = binding.Categories.GetEnumerator()

Get the ElementId of each Category contained within the CategorySet.

cat_ids = [cat.Id for cat in category_set]

Convert our Python list to a .NET List so that it will be a valid argument in our next method.

cat_ids_list = List[ElementId](cat_ids)

Remove all categories which cannot be used in creating a FilterRule.

cat_ids_valid = ParameterFilterUtilities.RemoveUnfilterableCategories(cat_ids_list)

Create our Equals rule with a value of 1 (or “Yes” in the context of a YesNo parameter). Then, convert the single rule to a .NET list of FilterRules.

rule = ParameterFilterRuleFactory.CreateEqualsRule(param_id, 1)
rules = List[FilterRule]([rule])

Create the actual ParameterFilterElement called “Yes No Test”.

TransactionManager.Instance.EnsureInTransaction(doc)
pfe = ParameterFilterElement.Create(doc, 'Yes No Test', cat_ids_valid, rules)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfe, pfe.Id
12 Likes

You posted this right when I was leaving yesterday, but I checked it out at home and wanted to wait to test it out before I posted. Thank you so much for spending so much time on this. It works great. Just need to edit a few things to get the right name and hopefully I can edit it to accept multiple rules.

1 Like

No problem. You will have to use different methods for each type of rule using the ParameterFilterRuleFactory. You can do something like this:

rule1 = ParameterFilterRuleFactory.CreateNotContainsRule(param_id_1, 'str', True)
rule2 = ParameterFilterRuleFactory.CreateContainsRule(param_id_2, 'sample string', True)
rules = List[FilterRule]([rule1, rule2])
2 Likes

EDIT1: I think I see it now the workset should be entered as a string, correct?
EDIT2: Yup, I added a workset node and a workset.Id node combined it with the value for the Yes/No and it worked.

Thanks, it looks like you were using separate inputs for the parameters and creating the rules in the python node. I’ve tried to do it with one input for parameters and another for values. I’m very close. It works if I comment rule2 out and remove it form the List[FilterRule] but when I have rule 2 it gives me this error, “File “”, line 31, in
TypeError: expected ElementId, got str”.

I’ve added the elem_id to the output and it looks like right to me.

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

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

from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument

param_id = IN[0]
elem_id = []
for elemid in param_id:
	elem_id.append(ElementId(elemid))
rule_value = IN[1]
# Get ParameterElement from Parameter's ElementId
param_elem = doc.GetElement(elem_id[0])
definition = param_elem.GetDefinition()
binding_map = doc.ParameterBindings
binding = binding_map.Item[definition]
category_set = binding.Categories.GetEnumerator()

cat_ids = [cat.Id for cat in category_set]
# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_ids)
cat_ids_valid = ParameterFilterUtilities.RemoveUnfilterableCategories(cat_ids_list)

rule1 = ParameterFilterRuleFactory.CreateEqualsRule(elem_id[0], rule_value[0])
rule2 = ParameterFilterRuleFactory.CreateEqualsRule(elem_id[1], rule_value[1])
# Convert single FitlerRule to List<FilterRule>
rules = List[FilterRule]([rule1, rule2])

TransactionManager.Instance.EnsureInTransaction(doc)
pfe = ParameterFilterElement.Create(doc, 'zAnno - Elem - Needs Callout', cat_ids_valid, rules)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfe, pfe.Id,elem_id

I think I might have to treat the second parameter differently.

2 Likes

The script I have provided works well for shared and project parameters, however a different approach should be taken for built-in parameters. Although it is currently working the way you have set it up, I have realized that this line:

param_elem = doc.GetElement(elem_id)

will return None if the parameter is a built-in parameter. I cannot find a way to get the categories to which a built-in parameter applies as it is not in the Document’s parameter bindings. So, while it happens to work currently, it’s sort of a false-positive. It just so happens that the first parameter you have provided shares a lot of categories with the Workset parameter, which I would expect to be nearly universal. However, if you try to use this with a much more specific built-in parameter like RBS_DUCT_PRESSURE_DROP (Visible name: “Pressure Drop”), it may not work.

1 Like

Thanks, I’ll keep that in mind. The first parameter is the limiting one anyways. When I first tried creating this I used a a simple code block with strings connected to a Category.byName node. I’m guessing it would be easier to modify the python node to take that instead of generating it within the node. It would also help when you want the filter to apply to only some categories that the parameter is assigned to.

I’ve tried plugging categories in and using this but I keep getting ‘List[object]’ object has no attribute ‘Id’.

cat = IN[2]
cat_id = [cat.Id for catid in cat]

# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_id)

I don’t need this to work however for my current script.

1 Like

That type of error implies that you are providing a list of elements rather than a single element. If you have two elements, elem1 and elem2, and you try to do this, it will raise and exception:

elements = [elem1, elem2]
id = elements.Id
# Raises an AttributeError

Instead, you have to access the actual elements individually:

elements = [elem1, elem2]
ids = []
for elem in elements:
    ids.append(elem.Id)
1 Like

I’ve been able to get this working so I can input categories and the name of the filter. It is not as automated as I’d like. I added instructions to some sections so I could remember how to add stuff to this script. Maybe I’ll try to get back around to making it be able to handle any amount of rules later.

It’s weird because the rule type is built into the command rather than being an argument.

EDIT 1: Also just realized that I need to remove the for loops if I’m working with a single rule because I got the can’t iterate over a non list or whatever.

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

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

from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument

# Inputs
param_id = IN[0]
rule_value = IN[1]
cat = UnwrapElement(IN[2])
filter_name = IN[3]

# Get Parameter's Id
elem_id = []
for elemid in param_id:
	elem_id.append(ElementId(elemid))

# Get Categories'  Id
cat_id = []
for catid in cat:
	cat_id.append(catid.Id)

# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_id)


# Create Rules
# Change Create___Rule section for rule type
# Add new rules by copying changing rule type and increasing integer
rule1 = ParameterFilterRuleFactory.CreateEqualsRule(elem_id[0], rule_value[0])
rule2 = ParameterFilterRuleFactory.CreateEqualsRule(elem_id[1], rule_value[1])

# Convert single FitlerRule to List<FilterRule>
rules = List[FilterRule]([rule1, rule2])

# Create actual Filter with rules, cat, and name from above
TransactionManager.Instance.EnsureInTransaction(doc)
pfe = ParameterFilterElement.Create(doc, filter_name , cat_ids_list, rules)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfe, pfe.Id,elem_id

EDIT2:
Just went through the processes of changing this to work with a single rule. I wonder how you would make this work with either case? Check to see if some inputs are lists and then do a if/then? anyway here it is:

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

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

from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument

# Inputs
param_id = IN[0]
rule_value = IN[1]
cat = UnwrapElement(IN[2])
filter_name = IN[3]

# Get Parameter's Id
elem_id = ElementId(param_id)

# Get Categories'  Id
cat_id = []
for catid in cat:
	cat_id.append(catid.Id)

# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_id)


# Create Rules
# Change Create___Rule section for rule type
# Add new rules by copying changing rule type and increasing integer
rule1 = ParameterFilterRuleFactory.CreateEqualsRule(elem_id, rule_value)

# Convert single FitlerRule to List<FilterRule>
rules = List[FilterRule]([rule1])

# Create actual Filter with rules, cat, and name from above
TransactionManager.Instance.EnsureInTransaction(doc)
pfe = ParameterFilterElement.Create(doc, filter_name , cat_ids_list, rules)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfe, pfe.Id,elem_id
2 Likes

Finding another issue. It looks like the create method for ParameterFilterElement had a (what the word? Type?) type removed between 2019 and 2020. When I tried running this is 2020 it would create the filter. I get this issue with the multiple and single rule versions: TypeError: expected ElementFilter, got List[FilterRule].

2019
filter%20yes%2003

2020
filter%20yes%2004

I’m guessing this is because of the category pull down they added to filters in 2020:
filter%20yes%2005

2 Likes

Looks like that particular method was deprecated and removed for 2020. I haven’t used 2020 yet for any projects, although I assume the related dynamo nodes have changed as well and might provide some initial guidance for how to do this. It looks like you have to provide ElementFilters rather than FilterRules although I don’t know specifically how to do this yet.

1 Like

The 2020 version of this is massively different than 2019 and earlier. This is working with one Paramter and value. I’m going to try to get my multi-parameter one working but I think I might have to add the values directly to the python script to due to how filters are made now(Probably not but it’s above my head I think).

I’ve left the 2019 version in there and commented it out:

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

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

from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument

# Inputs
param_id = IN[0]
rule_value = ElementId(IN[1])
cat = UnwrapElement(IN[2])
filter_name = IN[3]

# Get Parameter's Id
elem_id = ElementId(param_id)

# Get Categories'  Id
cat_id = []
for catid in cat:
	cat_id.append(catid.Id)

# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_id)

#####2019 rules################################
# Create Rules
# Change Create___Rule section for rule type
# Add new rules by copying changing rule type and increasing integer
#rule1 = ParameterFilterRuleFactory.CreateEqualsRule(elem_id, rule_value)

# Convert single FitlerRule to List<FilterRule>
#rules = List[FilterRule]([rule1])
###############################################


#####2020 Rules################################
#Parameter to Test ELEM_PARTITION_PARAM = Workset creates the FitlerableValueProvider
pWorkset = ParameterValueProvider(elem_id)

#Construct Filter Rule/Test
xWorkset = FilterElementIdRule(pWorkset, FilterNumericEquals(), rule_value)

#####2019 Create actual Filter with rules, cat, and name from above######
#TransactionManager.Instance.EnsureInTransaction(doc)
#pfe = ParameterFilterElement.Create(doc, filter_name , cat_ids_list, rules)
#TransactionManager.Instance.TransactionTaskDone()
########################################################

#####2020 Create Filter Rule############################
TransactionManager.Instance.EnsureInTransaction(doc)
demolitionworkset = []
demolitionworkset.Add(ElementParameterFilter(xWorkset))
icoldemolitionworkset = List[ElementFilter](demolitionworkset)
anddemolitionworksetfilter=LogicalAndFilter(icoldemolitionworkset)

#Make Acutal Filter
pfedemolitionworkset = ParameterFilterElement.Create(doc, filter_name, cat_ids_list, anddemolitionworksetfilter)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfedemolitionworkset, pfedemolitionworkset.Id, elem_id

If anyone is interested here is how I got it to work with multiple inputs. There is still some manualish code work that needs to be done when used like creating more ParameterValueProviders, FilterElementIDRules, and appending them together. I believe you can nest the logicalandfilter into an logicalorfilter or vice versa to make more complicated filters. I would like to make a version one day that works with any length of list and be able to have input for the rule type.

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

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

from System.Collections.Generic import List

doc = DocumentManager.Instance.CurrentDBDocument

# Inputs
param = IN[0]
value = IN[1]
cat = UnwrapElement(IN[2])
filter_name = IN[3]

# Get Parameter's Id
elem_id = []
for elemid in param:
	elem_id.append(ElementId(elemid))
	
# Get Rule Value
rule_value = []
for ruleValue in value:
	rule_value.append(ElementId(ruleValue))

# Get Categories'  Id
cat_id = []
for catid in cat:
	cat_id.append(catid.Id)

# Convert Python list to .NET List<ElementId>
cat_ids_list = List[ElementId](cat_id)

#Parameter to Test 
p1 = ParameterValueProvider(elem_id[0])
p2 = ParameterValueProvider(elem_id[1])

#Construct Filter Rule/Test
x1 = FilterElementIdRule(p1, FilterNumericEquals(), rule_value[0])
x2 = FilterElementIdRule(p2, FilterNumericEquals(), rule_value[1])

#Create Filter Rule
TransactionManager.Instance.EnsureInTransaction(doc)
xrule = []
xrule.Add(ElementParameterFilter(x1))
xrule.Add(ElementParameterFilter(x2))
icolx = List[ElementFilter](xrule)
andx = LogicalAndFilter(icolx)

#Make Acutal Filter
pfex = ParameterFilterElement.Create(doc, filter_name, cat_ids_list, andx)
TransactionManager.Instance.TransactionTaskDone()

OUT = pfex, pfex.Id, elem_id

Check out this post it helped me out a lot:

2 Likes