Script to Load and Insert Detail Family

Hi, all.
I’m new to pyRevit toolbar creation (just saw my first couple of videos on the topic recently), but am very excited for the opportunities ahead!
Could someone help me write a script for a pushbutton that will insert a detail family (or load one if it’s not already loaded)? The pseudo-code is as follows:

  1. click button to start script (already have this and the button already works - but the script itself does nothing yet).
  2. script will check if a specific detail family, say “symbol.rfa”, is loaded into current project model.
  3. if it is loaded, the script gets it ready to be inserted into the working view (similar to how the “Detail Component” in the Annotate tab already works).
  4. if is not loaded, the script will then load that detail family from a specific folder, say C:\pyRevit\MyTools.
  5. after loading it into the current project model, the script gets it ready to be inserted into the working view.

Any help would be greatly appreciated :slight_smile:

Hi @EBSoares
Check out this: GitHub - jmcouffin/pyRevit-BILT_NA_2022

That will get you there.
In the handout i talk about content bundles or content button.

This is a bundle type that lets you attach 1 or 2 families to a push button.

You can also install this demo toolbar nad figure out how it works and how it is structured clicking on the button + alt.

Hi, @Jean-Marc - thanks for the reply :+1:

It looks like the code there would require the Revit family to be actually located in the folder for the pushbutton itself. And I’m not sure if the program would even check if the family is already loaded in the project model before attempting to load the family.

Is there a way to write a script that will do that? Just like the pseudo-code above? That way we can keep all of our families in their usual folder (so it’s easy to make updates), write down the actual file path in the script, and go from there.

Thanks in advance

Check this: The Building Coder: Family API Add-in Load Family and Place Instances
The scenario 1 is what you are after.
It is c# but the logic is there.

You are right - after reading, it is exactly what I need :grinning:
I forgot to mention on the OP, but I am also new to Python (never wrote a thing in this language) and do not know how to convert C# to it in order to place it in the scipt.py file - could you give me a hand on that by any chance?

Got the code from Jeremy Tammik converted to Python (thanks Jeremy and Jean-Mark!).

Issue now is that it doesn’t seem to compile. I wish I knew enough about Python to fix it, but alas I can’t… Could someone help me figure this one out?

import clr
import os.path
import System
import Autodesk.Revit.DB as DB
import Autodesk.Revit.UI as UI


clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")


class CmdTableLoadPlace(UI.IExternalCommand):
	FamilyName = "family_api_table"
	_family_folder = os.path.dirname(System.Reflection.Assembly.GetExecutingAssembly().Location)
	_family_ext = "rfa"
	_family_path = os.path.join(_family_folder, FamilyName)
	_family_path = os.path.splitext(_family_path)[0] + "." + _family_ext


	_added_element_ids = []


	def Execute(self, commandData):
		uiapp = commandData.Application
		uidoc = uiapp.ActiveUIDocument
		app = uiapp.Application
		doc = uidoc.Document


		# Retrieve the family if it is already present:
		family = DB.FilteredElementCollector(doc).OfClass(DB.Family).FirstOrDefault(lambda f: f.Name == self.FamilyName)


	if not family:
		# It is not present, so check for the file to load it from:
		if not os.path.exists(self._family_path):
			TaskDialog.Show("Error", "Please ensure that the sample table family file '{0}' exists in '{1}'.".format(self.FamilyName, self._family_folder))
			return UI.Result.Failed


			# Load family from file:
			with DB.Transaction(doc, "Load Family") as tx:
				family = doc.LoadFamily(self._family_path)


		# Determine the family symbol
		symbol = None
		for s in family.Symbols:
			symbol = s
			# Our family only contains one symbol, so pick it and leave
			break


		# Place the family symbol:
		# Subscribe to document changed event to retrieve family instance elements added by the PromptForFamilyInstancePlacement operation:
		app.DocumentChanged += self.OnDocumentChanged
		self._added_element_ids.clear()


		# PromptForFamilyInstancePlacement cannot be called inside transaction.
		uidoc.PromptForFamilyInstancePlacement(symbol)


		app.DocumentChanged -= self.OnDocumentChanged


		# Access the newly placed family instances:
		n = len(self._added_element_ids)
		msg = "Placed {0} {1} family instance{2}{3}".format(n, family.Name, self.PluralSuffix(n), self.DotOrColon(n))
		ids = ", ".join(str(id.IntegerValue) for id in self._added_element_ids)
		TaskDialog.Show("Information", "{0}\n{1}".format(msg, ids))


		return UI.Result.Succeeded


	def OnDocumentChanged(self, sender, args):
		self._added_element_ids.extend(args.GetAddedElementIds())


	def PluralSuffix(self, n):
		return "" if n == 1 else "s"


	def DotOrColon(self, n):
		return ":" if n == 0 else "."

Hi @EBSoares ,

the problem with the script is you translated is… that is not a runnable pyrevit script.

The execution of a revit Add-in command and a pyRevit script is very different, even if they use the same Revit API.

The good news is that wirting python and a pyRevit script is easier than writing and compiling an add-in!

What you wrote is a Class and, in order to run, it needs to be instantiated and tell what method to execute.
But this is the Revit Add-in way, revit knows that a class that implements the IExternalCommand has the Execute method and launches it when you click the button.

Within pyrevit, you just need to write the code as it is a sequence of instructions to execute - a script.
Or, if you’re feeling fancy and like a bit of organization like me, you can create functions to group functionality in a logical manner and then call those functions at the end of the file.

Other things to note: you won’t have access to the commandData, but by using import pyrevit (or better, importing its submodules) you can access the current document with fewer lines. And in this specific case, the pyrevit functions that you need already retrieve the doc by themselves.

This is completely untested, but I’d rewrite the code like this:

import os.path

from pyrevit.revit import query
from pyrevit.revit.db import create
from pyrevit.revit.db import transaction 
from pyrevit import forms

def main(family_name, family_path):
    if query.get_family(family_name):
        # the family is already there, we exit early
        return

    if not os.path.exists(family_path):
        forms.alert(
            "Please ensure that the sample table family file '{0}' exists.".format(family_path),
            title="Error",
        )
        return

    with transaction.Transaction(name="Load Family"):
         family = create.load_family(family_path)
         symbol = next(s for s in family.Symbols)
         place_family_symbol(symbol)


def place_family_symbol(symbol):
   added_element_ids=[]

    def update_added_elements_ids(sender, args):
        nonlocal added_element_ids
        added_element_ids = args.GetAddedElementIds()

    app.DocumentChanged += update_added_elements_ids
    uidoc.PromptForFamilyInstancePlacement(symbol)
    app.DocumentChanged -= update_added_elements_ids
    # here you can build the message and show it with form.alert(msg, warn_icon=False)
    # ...


def build_family_path(family_name):
    # not sure this will work, you can print(family_folder) after the following line to see what it contains
    family_folder = os.path.dirname(System.Reflection.Assembly.GetExecutingAssembly().Location)
    return os.path.join(family_folder, family_name) + ".rfa"


# the last thing to do is to set the arguments and call the main function
family_name = "family_api_table"
family_path = build_family_path(family_name)
main(family_name, family_path)

If I may, given you other post, I would suggest you to start with a generic python course, there are many available online;
trying to write code by copying and pasting things found online feels like tossing wheels, nuts and bolts on a floor hoping to build a car :wink:

Umfortunately using pyrevit you have the double duty to know/understand two programming languages, since most of the actionable info around revit API is c# centered…

2 Likes

Hi, Andrea…
This is a fantastic, well-meaning, and in-depth explanation of everything I’ve been after in this forum - with a ready-to-use code (even if untested) to boot :smile:
Like you said, I’ve started reviewing a Python course a couple weeks ago (I’ve seen some videos already, but this webpage is my favorite so far).
A friend of mine who is really good at Python (guy’s a genius) has offered to help me work on this code too, so I’ll pass on what you wrote above to him in case it helps some more in any way.
Once it’s all done and tested I’ll post the final code here in case someone else in the future has the same need.
Thank you for your kindness and God bless you!
Edgar

1 Like