1

Is there a way I can copy a Filter Element Collector object? For example, the original object is pointing at 0x000000000000156B and I want the copied object to be pointing at a different location so I can keep making modificacions without changing the original object.

Here's some code to illustrate my idea:

Col1 = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls)
#Some code to copy the object and assign it to Col2
Col2 = Col2.WhereElementIsNotElementType().ToElements() #Changing Col2 shouldn't change Col1.

I know there's no such method within the FilteredElementCollector class, but there should be a way to do this, right? I also read about deepcopy but couldn't get it to work on Revit.

Any help will be highly appreciated, Thanks!

  • 1
    Revit Element data can not exist outside of Revit document. You can't make a copy of a Revit element without actually having a second copy of the element inside the Revit model. For example, you need to duplication a wall element in Revit model (be aware of all the collision warnings this might cause), and then edit the second element – Ehsan Iran-Nejad Jun 16 '20 at 02:15
  • Yes, this makes sense. I just thought maybe a collector could be copied. If I define two times the same collector I get two different FilteredElementCollector objects. It'll be easier if instead of having to define the collector again I could just copy the one I already have, but, as you say, this may not be possible. – ANDRES RAMOS Jun 20 '20 at 17:53
  • Every time you call a filtering method (e.g. `.OfCategory()` or `.OfClass()`) on a collector instance, they return a modified version of that instance. So even if you store a more generic collector in a variable, all subsequent calls to filtering methods will change the original collector as well – Ehsan Iran-Nejad Jun 20 '20 at 23:59
  • 1
    Got it! Thank you so much! – ANDRES RAMOS Jun 24 '20 at 18:37

2 Answers2

0

I normally use the FilteredElementCollector method by wrapping it in a Python list. Then you can combine, refine, split, copy, sort - bascially do anything you want to it with all the ease that Python offers.

For your problem above, you could create the FilteredElementCollector, and spin it off into lists as required:

rawWalls = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls)

Col1 = list(rawWalls)
print 'There are',len(Col1),'wall types+instances in Col1'

Col2 = list(rawWalls.WhereElementIsNotElementType().ToElements())
print 'There are',len(Col2),'wall instances in Col2'
Callum
  • 578
  • 3
  • 8
  • Hello there, thanks for your answer. I don't think this achieves what I'm looking for. Col1 and Col2 still point at the same FilteredElementCollector object. If after you add Col3 =list(rawWalls.WhereElementIsElementType().ToElements()), you don't get any elements because the collector is already filtered out. My idea is to have a static collector (let's called it that), create a copy and then apply all filters to those copies so the original (static) collector remains unchaged. – ANDRES RAMOS Jun 20 '20 at 18:19
  • Ah I see. So in that case Id just make a new `FilteredElementCollector` every time I needed it - they are blazingly quick. Sorry I couldnt be more help – Callum Jun 21 '20 at 20:49
  • No worries, I liked the idea of wrapping the collector in a python list. That'll come quite handy afterwards. And yes, it's quite fast and easy to define the collector all over again. Thank you! – ANDRES RAMOS Jun 24 '20 at 18:37
0

As you've already figured out, it's not possible to create a copy of a FilteredElementCollector. However, you could create a functionally identical one by recording which methods are called on the original and duplicating those method calls when you need to make a copy. The class below does just that:

class CopyableFilteredElementCollector(FilteredElementCollector):

    def __init__(self, doc):

        # Initialize the underlying FilteredElementCollector
        FilteredElementCollector.__init__(self, doc)

        # Save the document
        self._doc = doc

        # Calls to methods that constrain the FilteredElementCollector will be recorded here
        self._log = []

    def copy(self):

        # Create a brand new instance
        copy = CopyableFilteredElementCollector(self._doc)

        # Replay the log on the new instance
        for method_name, args in self._log:
            getattr(copy, method_name)(*args)

        # The copy now references the same document as the original,
        # and has had the same methods called on it
        return copy

We need to override each method that restrict the elements returned by the FilteredElementCollector to record its invocation in the log. The most straightforward way to do that would be by defining override methods in the class body like this:

    def OfCategory(self, category):

        # Add an entry to the log containing the name of the method that was called
        # and a tuple of its arguments that can be expanded with the splat operator
        self._log.append(('OfCategory', (category,)))

        # Call the original method
        FilteredElementCollector.OfCategory(self, category)

        # Return self just like the original method does 
        return self

Defining an override for every single method gets repetitive, so let's employ some metaprogramming voodoo instead:

# Methods of FilteredElementCollector that constrain which elements it returns
constraint_methods = [
    'OfCategory',
    'OfClass',
    'WhereElementIsElementType',
    'WhereElementIsNotElementType',
    'WherePasses',
    # et cetera
]

# A factory function that produces override methods similar to the one defined above
def define_method(method_name):
    def method(self, *args):
        self._log.append((method_name, args))
        getattr(FilteredElementCollector, method_name)(self, *args)
        return self
    return method

# Create an override for each method and add it to the CopyableFilteredElementCollector class 
for method_name in constraint_methods:
    setattr(CopyableFilteredElementCollector, method_name, define_method(method_name))

I won't go as far as to claim that my approach is a good idea, but it's an option if your use case requires making copies of a FilteredElementCollector rather than creating new ones from scratch.