I’ve been building a small add-in that creates steel beam and column types from an Excel schedule, and one bug took me a while to understand, so I’m sharing it in case it saves someone else the time.
The setup. For each row in the schedule I look up the target family (for example a UB family), then create the type. If the size doesn’t exist in the project yet, I load just that one type from the Revit library’s type catalog:
doc.LoadFamilySymbol(rfaPath, "310UB40.4", loadOptions, out FamilySymbol loaded);
The problem. After that call, Family and FamilySymbol objects I had collected before the transaction sometimes threw InvalidObjectException when I used them again for the next row. As far as I can tell, loading a type into a family that is already in the project reloads the family, so the managed objects you were holding no longer point to a valid element, even though the ElementId is often the same.
The pattern that fixed it. Collect ElementIds up front, and re-fetch the element from the document every time you use it:
// Before the transaction: keep ids, not objects
var targetIds = plan.ToDictionary(p => p, p => p.Targets.Select(f => f.Id).ToList());
Family Fam(ElementId id) => doc.GetElement(id) as Family;
using (var t = new Transaction(doc, "Create steel types"))
{
t.Start();
foreach (var row in plan)
{
foreach (ElementId famId in targetIds[row])
{
if (row.NeedsLoad &&
doc.LoadFamilySymbol(row.RfaPath, row.SectionName, loadOptions, out FamilySymbol loaded))
{
// anything fetched before this line may be stale now
}
Family fam = Fam(famId); // re-fetch after every load
if (fam == null) continue;
// ... find or duplicate the type in fam ...
}
}
t.Commit();
}
Two related details that helped:
- Load the single standard type rather than duplicating and editing parameters.
LoadFamilySymbol(path, typeName, ...)reads the row from the family’s .txt type catalog, so all section parameters (and Section Name Key) are correct. Duplicating an arbitrary type and writing values works too, but it’s easy to miss a parameter. - Use an
IFamilyLoadOptionsthat returnsoverwriteParameterValues = false, so loading a new type doesn’t reset values on types the user already has:
class LoadOptions : IFamilyLoadOptions
{
public bool OnFamilyFound(bool familyInUse, out bool overwriteParameterValues)
{ overwriteParameterValues = false; return true; }
public bool OnSharedFamilyFound(Family sharedFamily, bool familyInUse,
out FamilySource source, out bool overwriteParameterValues)
{ source = FamilySource.Family; overwriteParameterValues = false; return true; }
}
Has anyone found a cleaner way to handle this, for example batching all loads before any duplication? I’d be interested in how others structure it.
(For context, this is from a free structural add-in I’m building in C#. The same thing applies if you call LoadFamilySymbol from a pyRevit script. Happy to share more of how the Excel side works if useful.)