Select Face with Python

Hello, I am trying to build a script that places a family by Face at the end of MEP connections. When I try to place the family with the node Select Face it works perfectly. When I try to automate this with Python I don’t get the same result, it is always a face that is not usable as a host.

Does anyone know how the Dynamo native node can be replicated with Python?

The code used in the screenshot is the following:

# Dynamo Python Script: Select a single face in Revit
# Works inside Dynamo with Revit API access

import clr
# Import Revit API
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import \*

# Import Revit Services
clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# Import Revit UI API
clr.AddReference("RevitAPIUI")
from Autodesk.Revit.UI import \*
from Autodesk.Revit.UI.Selection import ObjectType

# Get current document and UI document
doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

# Try to pick a face
try:
    # Prompt user to select a face
    ref = uidoc.Selection.PickObject(ObjectType.Face, "Select a face")
    
    # Get the element from the reference
    element = doc.GetElement(ref.ElementId)
    
    # Get the geometry object (face) from the reference
    geom_obj = element.GetGeometryObjectFromReference(ref)
    
    # Output both element and face
    OUT = (element, geom_obj)

except Exception **as** e:
    # If user cancels or error occurs
    OUT = "Selection cancelled or failed: {}".format(str(e))

Any help would be appreciated. Thank you

You are returning the Revit API planar face. You need a Dynamo wrapper on it instead. Try adding a .ToProtoType() at the end of the geom_obj line.

Thanks @jacob.small ,with that addition seems to pick up the surface but when I try to use it to place the family I get the following error:

The problem then is that if i use ImportInstance.ByGeometry, the family instance is placed in a symbol family, not the actual fitting (host) that I want to get the data from.

With the native Dynamo node Select Face I don’t have that problem.

You’re making your life much harder here by bouncing between Python and Dynamo.

The ‘face’ method for FamilyInstance.ByFace requires a more direct conversion than you’ll get with ToProtoType() on the reference itself due to how the Dynamo wrapper works.

What is the purpose of moving to a Python based selection method?

1 Like

The purpose is to automate the transfer of data from the host to the family and the placing of the families as I have hundreds of connections.
The other way would be Select Faces and placing the family, but I cannot get to read the elements where the Faces are picked up from.

1 Like

Which year of Revit are you working in?

Revit 2025

Finally I got the element from the reference of the surfaces with the following:

# Dynamo Python

import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
from Autodesk.Revit.DB import \*
from RevitServices.Persistence import DocumentManager

doc = DocumentManager.Instance.CurrentDBDocument

# IN\[0\]: a Reference to a face (from Select Face or constructed)
surfaces = IN\[0\] if isinstance(IN\[0\], list) else \[IN\[0\]\]
result = \[\]

for surface in surfaces:
    try:
        reference = surface.Tags.LookupTag("RevitFaceReference")
        result.append(reference)
    except:
        result.append(None)

        
OUT = result

I can get the data that way.Preformatted text

So I see @c.poupin is typing, which means we may have an amazing solution coming that beats every possible issue 10x over… However, generally I recommend staying in the Revit API once you get into it, and since you have to get into it, I’ll see if I can code up a CPython solution between calls. : )

2 Likes

What type of family is it? Cap Open Ends? MEP Terminal?
Do you share a simple Revit file (or a screenshot) so we can understand a little better?

2 Likes

the family is a generic model to represent the connection with all the data from the actual fitting, so it can be placed also in different categories with and schedule the data of all of them together

I admit I don’t understand the purpose of the process (placing IoT devices?).

But here is an example in Python.

The placement point corresponds to the mouse click. The Python code is written using the latest PythonNet3 engine and is available as a package in Revit 2025-2026 (and by default in 2027+).

place_familyinstance on faces

import clr
import sys
import System
from System.Collections.Generic import List
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument

class FaceSelection(ISelectionFilter):
    __namespace__ = "FaceSelection_Dhizi7"
    def AllowElement(self, e):
        return True

    def AllowReference(self, ref, point):
        elem = doc.GetElement(ref)
        face = elem.GetGeometryObjectFromReference(ref)
        if isinstance(face, DB.PlanarFace):
            return ref.ElementReferenceType == ElementReferenceType.REFERENCE_TYPE_SURFACE

        return False

familyType = UnwrapElement(IN[0])
out  = []
#
TaskDialog.Show("Selection", "Pick Point on several Planar Faces")
i = 0
while i < 100:
    i += 1
    try:
        ref = uidoc.Selection.PickObject(ObjectType.PointOnElement, FaceSelection(), "Pick Point on a Planar Face")
    except Exception as ex:
        break
    # get Face Geometry
    sel_elem = doc.GetElement(ref)
    elem = sel_elem
    face = elem.GetGeometryObjectFromReference(ref)
    
    selectPoint = ref.GlobalPoint
    
    TransactionManager.Instance.EnsureInTransaction(doc)
    if not familyType.IsActive:
        familyType.Activate()
    try:
        new_instance = doc.Create.NewFamilyInstance(ref, selectPoint, face.FaceNormal , familyType)
        out.append(new_instance)
    except:
        new_instance = doc.Create.NewFamilyInstance(ref, selectPoint, face.FaceNormal.CrossProduct(XYZ.BasisX) , familyType)
        out.append(new_instance)
    #
    TransactionManager.Instance.TransactionTaskDone()
    doc.Regenerate()
    
OUT = out
4 Likes

haaha :wink: love it :wink:

1 Like

As predicted, @c.poupin posted a great solution here. :slight_smile:

The version I was working on had an input to surpress bindings allowing multiple runs in the same instance, as well as a few preliminary checks to see if the family type was valid for the intended hosting method. You might be able to combine the two and bypass the second try/except in his code.

########################################
############## Properties ##############
########################################
__author__ = 'Jacob Small'
__version__ = '0.1.0'
__description__ = "Places a family instance at the center of the hosted face"
__DynamoBuilds__ = "3.5"
__ReleaseNotes__ = "POC Only - test thuroughly before implementing in production."
__Dependancies__ = "None"
__Copyright__ = "2025, Autodesk Inc."
__license__ = "Apache 2"



########################################
### Configure the Python environment ###
########################################
### standard imports ###
import sys #add the sys class to the Python environment so we can work with the sys objects
import clr #add the CLR (common language runtime) class to the Python environment so we can work with .net libraries

### basic Dynamo imports ###
clr.AddReference('ProtoGeometry') #add the Dynamo geometry library to the CLR
from Autodesk.DesignScript import Geometry as DG #add the Dynamo geometry class using the alias DG, to ensure no overlap between class calls in Revit or Dynamo

### Dynamo Revit imports ###
clr.AddReference("RevitNodes") #add Dynamo's Revit nodes library to the clr
import Revit #import Dynamo's Revit node class
clr.ImportExtensions(Revit.Elements) #add the element conversion methods to the CLR
clr.ImportExtensions(Revit.GeometryConversion) #add the geometry conversion methods to the CLR
clr.AddReference("RevitServices") #add the Revit services library to the CLR
import RevitServices #import the Revit services class to the Python environment
from RevitServices.Persistence import DocumentManager #import the document manager class to the Python environment 
from RevitServices.Transactions import TransactionManager #import the transaction manager class to the Python environment 

### Revit API imports ###
clr.AddReference("RevitAPI") #add the Revit API to the CLR
import Autodesk #add the Autodesk class to the Python environment 
from Autodesk.Revit.DB import * #import every class of the Revit API to the Python environment
from Autodesk.Revit.UI.Selection import ObjectType #import the object type class for selection control



#########################################
###### Global variables and inputs ######
#########################################
doc = DocumentManager.Instance.CurrentDBDocument #the current Revit document
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument #the current UI document
TransactionManager.Instance.EnsureInTransaction(doc) #start transaction earlier than usual so we can activate the family if needed
rerun = IN[0] #boolean input to allow sucessive runs
familyType = UnwrapElement(IN[1]) #import the family type from IN[0] of the Dynamo environment and convert to native Revit elements
if not isinstance(familyType, FamilySymbol): sys.exit("\r\rProvide a family symbol as the first input.\r\r") #ensure the family instance is a valid object
if not familyType.IsActive: familyType.Activate() #activates the familytype if not already active
if not familyType.Family.FamilyPlacementType == FamilyPlacementType.WorkPlaneBased:
    sys.exit("\r\rFamily is not a face based family - select an alternative.\r\r")
bypassBindings = IN[2] #if you want to bypass bindings to allow successive creation
if not isinstance(bypassBindings, bool):
    sys.exit("\r\rProvide a boolean value as the second input.\r\r")



#########################################
########### Code starts here ############
#########################################
try:
    reference = uidoc.Selection.PickObject(ObjectType.Face, "Select a face") # Prompt user to select a face
    result = False #set result to False
except Exception as e: #if there was an exception
    result = e #set the result to the exception
if not result: #if result isn't true
    element = doc.GetElement(reference.ElementId) #get the element from the reference
    face = element.GetGeometryObjectFromReference(reference) # Get the face from the reference
    surfaceExtents = face.GetBoundingBox() #get the extents of the surface, a bounding box UV object
    surfaceMid = surfaceExtents.Max.Subtract(surfaceExtents.Min).Divide(2).Add(surfaceExtents.Min) #get the mid point of the bounding box UV by subtracting the minimum value from the maximum, dividing by two, and then adding the minimum 
    location = face.Evaluate(surfaceMid) #convert the UV of the mid point to a XYZ 
    orientation = face.ComputeDerivatives(surfaceMid).BasisX #get the vector fo the U axis at the location
    newInstance = doc.Create.NewFamilyInstance(reference, location, orientation, familyType) #generate the new instance
    result = newInstance.ToDSType(bypassBindings) #set resultthe new family instance, converted to a Dynamo owned object if bypass bindings was 'false'



#########################################
##### Return the results to Dynamo ######
#########################################
TransactionManager.Instance.TransactionTaskDone() #end transaction
OUT = result #return the result to the Dynamo environment
2 Likes

The purpose is to place those generic models automatically, without selecting faces, just markers where they have to be placed, so the process can be automated, included the transfer of data from the host to the family.

In some categories, like pipe fittings and duct fittings, I wasn’t able to select the face but using the select face. With time I hope to complement your code with an input of the faces, points and references I need to complete automating the positioning, without going one by one, as there are hundreds to locate in some projects (company workflows)

1 Like

Thank you all