I want to get the color of an element in active view.
If the color is set by the color of the material or by element override, there is no problem in doing so.
But if the color of the element is set by a view filter, I don’t know how to obtain the color of the element through Dynamo.
I’m looking for some parameter that tells me the color of the element in the view. I am also looking for what elements are affected by a filter, but for now I have not succeeded.
I can get the filters with node View.Filters, but I do not know what elements are affected by those filters. I’m looking for a function like “Element.ViewFilters”, but I can’t find anything similar in the API.
I agree, I can’t see a property of an element which is ‘has been overriden by view filter’. Though obviously Revit does know this.
Unfortunately I guess that means you would need to replicate the filter… Retrieve the filter’s properties and run it through the elements in the view…
If you’re brave enough and have the skills, someone’s had a go here… Though given the number of permutations it would be difficult to catch them all without some clever coding…
Hope that’s of interest,
Mark
Edit: It can be translated to Python with a bit of effort…
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference("RevitNodes")
import Revit
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
filters = UnwrapElement(IN[0])
for f in filters:
rules = f.GetRules()
catid = f.GetCategories()
for c in catid:
cats = 'Filter applied to ' + Revit.Elements.Category.ById(c.IntegerValue).Name;
for r in rules:
if True == r.GetEvaluator().GetType().Equals(FilterStringContains):
evaluator = 'Filter Type = Contains'
ruleString = 'Filter Value = ' + r.RuleString
OUT = evaluator, ruleString, cats,
In the case where you have some filter overlap or where an element falls under more than one filter, you have the keep in mind the order of the filters and not let the same element fall under multiple categories.
Good point Kenny, unfortunately they seem to be returned in the order they were added, not the order they appear in the list. There must be some kind of view filter sequence number that Revit uses, but I can’t see that property in views?
Yeah, I am lost as to how you would get this done. It seems like a really difficult question. Your best luck is asking a true API expert. Maybe the Autodesk’s Revit API forums would be a good place to ask.
While you’re here… I’ve worked on the code a bit more, but the difficulty is that we’re having to test individually for each part of the constructor and these are all different classes… FilterStringRule Class etc.
Do you have a cute way of looping to avoid the individual testing? The 2019 addition of AND, OR etc. will make this worse…
Edit: Perhaps using definitions maybe? It seems inelegant to copy whole bunches of code around
Cheers,
Mark
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference("RevitNodes")
import Revit
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
filters = UnwrapElement(IN[0])
name = []
catid, cats, cati, catoutP = [], [], [],[]
rules = []
evaluator = []
ruleString = []
for f in filters:
#get filter name
n = 'Filter Name - ' + f.Name
name.append(n)
#get category id for use in getting category
catid.append(f.GetCategories())
for cat in catid:
#list comprehension allows us to map the For over the base level of the list, this keeps the list structure
catoutP.append([Revit.Elements.Category.ById(c.IntegerValue).Name for c in cat])
for o in catoutP:
#we want to join all the categories that each filter applies to into a single string
c = 'Category Classes - ' + (', '.join(o))
cati.append(c)
rules.append(f.GetRules())
for rule in rules:
for r in rule:
#get the value which the filter is looking for, this can be different types... strings, booleans... this example just looks for strings, but it should be expanded to cover all types
ruleString.append('Filter Value = ' + r.RuleString)
#similar to the rule value, the evaluator could be multiple types, this example just looks for string contains and string equals
if True == r.GetEvaluator().GetType().Equals(FilterStringContains):
evaluator.append('Filter Type = Contains')
elif True == r.GetEvaluator().GetType().Equals(FilterStringEquals):
evaluator.append('Filter Type = Equals')
else:
evaluator.append('Filter Type Fail')
#we want to gather all the output information together
outInfo = []
outInfo = [name, evaluator, ruleString, cati]
#we want to transpose the order of the output information so that it becomes by each filter, rather than by each output
matrix = zip(*outInfo)
outPut = []
for row in matrix:
outPut.append(row)
OUT = outPut
I tried using a dictionary plus a function for it. Fortunately a few of them use the same name of a function but not all so I still have to use some if statements but it should be easy to add on to it if you come across more filter types than what I had.
Apparently there is a class for inverse filter rules, basically means does not (contain, equal, etc.) that you have to dig one level deeper in, so I just go through the function again but with .GetInnerRule() and added a 'Not - ' infront of it.
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('RevitNodes')
import Revit
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
filtertypes = {
"Autodesk.Revit.DB.FilterDoubleRule" : 0,
"Autodesk.Revit.DB.FilterElementIdRule" : 0,
"Autodesk.Revit.DB.FilterIntegerRule" : 0,
"Autodesk.Revit.DB.FilterStringRule" : 1,
"Autodesk.Revit.DB.FilterInverseRule" : 2}
def filterTesting(rClass):
if filtertypes[rClass.ToString()] == 0:
return rClass.RuleValue
elif filtertypes[rClass.ToString()] == 1:
return rClass.RuleString
elif filtertypes[rClass.ToString()] == 2:
return "Not - " + str(filterTesting(rClass.GetInnerRule()))
else:
return rClass.ToString()
filters = UnwrapElement(IN[0])
name = []
catid, cats, cati, catoutP = [], [], [],[]
rules = []
evaluator = []
ruleString = []
for f in filters:
rules.append(f.GetRules())
OUT = []
for rule in rules:
for r in rule:
OUT.append(filterTesting(r))
The best way to do the whole script, though, would probably be to put your second half all in a function and then just run the function on all of the parameter filter element in the first for loop.
Retrieving the color might be a bit tricky. Since we have to deal with multiple cases.
The element may defined by parameters such as:
Be defined by a view-specific override.
Be defined by a view filter
2a. Be defined by multiple conflicting view filters (lets ignore this for now since it overcomplicates things)
Be defined by the material.
Now if you wish to get the color (in general) I would suggest:
Check if a view-specific override applies .
Figure out which filters applies to the element for the view.
Check the filter overrides of the filter.
Get the color is the (for instance) projectionfillcolor is valid (of either override or filter).
If no override or filter applies get the color for the materials.
For the filter consider solely step 2-4.
Now since I thought it would be a fun exercise I have written a (probably way too complicated) script. Apologies for the lazy imports, hope it will be useful to some people and would like some input as to making it better
Unfortunately, I was unable to get around the concern of having to evaluate each of the filters for every element as raised by Mark.Ackerley.
#2019, Lukas Mikkelsen
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# Import Element wrapper extension methods
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.Elements)
# Import ToProtoType, ToRevitType geometry conversion extension methods
clr.ImportExtensions(Revit.GeometryConversion)
# Import DocumentManager and TransactionManager
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
clr.AddReference('DSCoreNodes')
import DSCore
from DSCore import *
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
# Import RevitAPI
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import System
from System import Array
from System.Collections.Generic import *
import sys
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)
import os
#The inputs to this node will be stored as a list in the IN variable.
if(isinstance(IN[0],list)):
elements = UnwrapElement(IN[0])
else:
elements =[UnwrapElement(IN[0])]
output = []
for element in elements:
activeview = doc.ActiveView
override = activeview.GetElementOverrides(element.Id)
color = None
if override.ProjectionFillColor.IsValid:
color = ','.join(override.ProjectionFillColor.Red.ToString(),override.ProjectionFillColor.Blue.ToString(),override.ProjectionFillColor.Green.ToString())
else:
fels = activeview.GetFilters()
for fel in fels:
filtercats = [x for x in doc.GetElement(fel).GetCategories()]
# Check if element category corresponds to filter category
if element.Category.Id in filtercats:
filterrules = doc.GetElement(fel).GetRules()
rulepassed = []
# Check if element passes all filter rules
for frule in filterrules:
rulepassed.append(frule.ElementPasses(element))
if all(rulepassed):
override = activeview.GetFilterOverrides(fel)
# BEWARE: THIS PART ASSUMES THAT MULTIPLE FILTERS DOES NOT COLLIDE IN REGARDS TO COLOR DEFINITION OF THE ELEMENT
if override.ProjectionFillColor.IsValid:
color = ','.join([override.ProjectionFillColor.Red.ToString(),override.ProjectionFillColor.Blue.ToString(),override.ProjectionFillColor.Green.ToString()])
else:
pass
# Otherwise element does not satisfy the filter
else:
pass
# Otherwise the element category does not correspond to the filter category
else:
pass
# Check if color has been previously defined.
# If the color is retrieved based on material, our element may consist of multiple materials.
if not color:
color = list()
mats = [doc.GetElement(x) for x in element.GetMaterialIds(False)]
for mat in mats:
color.append(','.join([mat.Color.Red.ToString(),mat.Color.Blue.ToString(),mat.Color.Green.ToString()]))
else:
pass
output.append(color)
#Assign your output to the OUT variable
OUT = output
We have used the script to get our filter rules and it works fine for the rules with the AND function.
However, for the filters with the OR function, the script is failing. Is there an other way to get those filter rules also?