Family open, add paint to geometry , then reload?

Is it possible to open a set of families, add “paint” to a geometry face, inside the family, then load it back in the project with Dynamo or RiR, API … ?

Really apricate an answer to this asap.

Yes, it is possible with Dynamo and the Revit API.

1 Like

Is it possible to get some guidance in the right direction. I can’t seem to find much on this online.

This example works fine when painting system families inside a project.

However, I am dealing with some complex façade geometry, classified as a generic model. We are attempting to extract specific surface area quantities.

Here’s how I see the process:

Retrieve desired families from active document
Open each family
Analyze geometry and select specific surfaces or faces
Paint selected surface
Load back into the project.

I do know how-to analyze the geometry once I extract it and point to the right surface but I’ve never scripted or worked with geometry inside the family editor, so I’m unsure how to handle this.

I attached an example project with some cubes, if anyone can help me do a simple script to “paint” a face of a cube and load it back intro the project, or point me in the right direction …


RVT2022_TestProjectPaint.rvt (628 KB)

I’ll provide some guidance, but won’t be able to code this up for you - it’d be a big lift for someone to code without full context.

The basic outline you’ve identified is correct from what I can see.

  1. Find the families to edit
    a) This should be easy enough for you.
  2. Open the family
    a) This is harder; I don’t recommend loading these into memory on an individual node, but in a single workflow; Either a custom node to process each family from here to step 5, or a bit of Python to do the work. I would go Python personally as it’s a bit more robust in terms of memory management.
  3. Find the surface to paint
    a) This is likely the hardest bit, as you can’t just draw a box on it; what about each surface makes it the thing you are interested in? The normal? The material? Something else? Best to tackle this and step 4 in a Dynamo graph set to work in the open family file (open the family, then launch Dynamo while in it)
  4. Apply the paint
    a) If you can’t do this manually in the active document (once in the family) you wont’ be able to bulk do it. The code sample should be a fairly straight forward implementation of this, but you may want to use a parameter instead (which would require inserting the step of adding the shared parameter to the family as 3.1)
  5. Load the family into the project (and close it)
    a) There are several examples of this on the forum and in various packages which are Python based. Best to look there as a start. Careful with the duplicate family load options interface - in CPython 3 (which i’d recommend using for forward compatibility) you’ll need to implement a unique value for the namespace of the class on reach run - a randomly generated unique ID can accomplish this fairly readily.

Break the items down, tackle them one at a time, and post for guidance when you get stuck.

Note that the learning curve here is fairly high so you might be better off doing it by hand if you have a deadline, and coming back to it when you’re freed up a bit.

1 Like

Thanks for the fast reply ! I will give it a go.

This may not be what you are after, but may help. My company also deals with complex shapes. Along a similar line, we have to report the area of the finished surfaces. For that, we have a script where the user selects all of the surfaces on one panel, the area of all selected surfaces are added together. From there we filter to all instances of that family within the project and write the sum of the surfaces to a parameter.

@jacob.small

Ok, I have something working -ish …
Ignoring step 3 (let’s assume we want to paint all surfaces … )

I’m selecting the families based on string input …
Geting the docs …
Then iterating and appling paint, saving them, lading them back in … I get this error …

Warning: IronPythonEvaluator.EvaluateIronPythonScript operation failed. 
Traceback (most recent call last):
  File "<string>", line 71, in <module>
Exception: The file is read-only, can not be saved.

I’m saving on the desktop so I do have permission …
It only saves and load back the first family.

Here is the code from the last node:

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

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

# The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN

famDocs = IN[0]  # The family documents
materialName = IN[1]  # Material name, e.g., 'X'
savePath = IN[2]  # Path to save the family documents

# Get current project document
doc = DocumentManager.Instance.CurrentDBDocument

output = []

# Define the FamilyLoadOptions class
class FamilyLoadOptions(IFamilyLoadOptions):
    def OnFamilyFound(self, familyInUse, overwriteParameterValues):
        return True

    def OnSharedFamilyFound(self, sharedFamily, familyInUse, source, overwriteParameterValues):
        return True

for famDoc in famDocs:
    # Get all elements in the family document
    collector = FilteredElementCollector(famDoc).WhereElementIsNotElementType()

    # Check if material exists in the family document
    material = None
    for m in FilteredElementCollector(famDoc).OfClass(Material):
        if m.Name == materialName:
            material = m
            break

    if material is None:
        output.append('Material not found: ' + materialName)
    else:
        # Start Transaction
        TransactionManager.Instance.EnsureInTransaction(famDoc)

        for elem in collector:
            geoElement = elem.get_Geometry(Options())
            
            if geoElement is not None:  # Check if the element has geometry
                for geo in geoElement:
                    if isinstance(geo, Solid):  # Check if the geometry element is a Solid
                        for face in geo.Faces:
                            try:
                                # Paint the face with the material
                                famDoc.Paint(elem.Id, face, material.Id)
                                output.append("Success for element: " + str(elem.Id))
                            except:
                                output.append("Failed for element: " + str(elem.Id))

        # Close Transaction
        TransactionManager.Instance.ForceCloseTransaction()

    # Close the family document and load it back into the project
    famDocPath = ""
    if famDoc.PathName:  # Check if the document has a path
        famDocPath = famDoc.PathName
        famDoc.Close(True)
    else:
        famDocPath = savePath + "\\" + famDoc.Title
        famDoc.SaveAs(famDocPath)
        famDoc.Close(False)

    # Load family back into the project
    TransactionManager.Instance.EnsureInTransaction(doc)
    loadOpts = FamilyLoadOptions()  # Default options
    doc.LoadFamily(famDocPath, loadOpts)
    TransactionManager.Instance.TransactionTaskDone()

# Assign your output to the OUT variable.
OUT = output

RVIT2022_TestPaintCubes.dyn (19.8 KB)

RVT2022_TestProjectPaint.rvt (804 KB)

Ideally, we avoid selecting anything manually.
The issues is we have multiple workflows for façade elements. When all revit native, no problem. But Typically Grasshopper → BEAM to Revit then QT extraction happens on the Revit side for LCA assessment.

Issue with this is we need surfaces as a “Revit material” and generic models can provide only volumes. Here is where paint tool comes into play. We bake some point coordinates in our Revit families as a string then we match those points to select the correct face to paint.

The family documents are being passed from the previous node and as sucha re in the Revit environment. Because of this they are owned there and you won’t be able to save over them while this is the case. Move into a single Python node and things should be more doable as you’d be able to do the processes open > edit > save > load > close in one loop, ensuring you never open the document twice (reducing run time and memory consumption as an added benefit)

I’d revisit the Grasshopper > Beam > Revit workflow and utilize something else so you can get native content. Step 3 is the hard part in my eyes (as noted above), and because of how you’re pushing content you’re likely going to face a plethora of inconsistencies with your geometry analysis.

Thanks for the fast reply. I will try it over the weekend and get back with an update. Enjoy your weekend!

1 Like