Huge gap in Autodesk Revit Family API I cannot seem to get past… I cannot create or tweak labels in Revit Families. The closest I have gotten is copying a Label parameter started from a seed file.
@jacob.small and @jeremytammik or @SeanP - any ideas on how we could programmatically populate labels in the REvit Family API?
SYMB-__-Applebees-Notes-Draft-TEST.rfa (412 KB)
# -*- coding: utf-8 -*-
# Dynamo Revit 2025 (Dynamo 3.3) - CPython3
# No inputs:
# - Read Family parameter "Note" (current type)
# - Prefer REAL Label control (bind its "Label" ElementId to FamilyParameter.Id)
# - Fallback to TextElement (set TextElement.Text)
# - Copy N times for visual verification
import clr
from RevitServices.Persistence import DocumentManager
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import (
FilteredElementCollector,
ElementTransformUtils,
StorageType,
Transaction,
XYZ,
)
doc = DocumentManager.Instance.CurrentDBDocument
# --- hardcoded defaults (no IN[]) ---
TARGET_FAMILY_PARAM = "Note"
COPIES = 1
DX_IN = 2.0
DY_IN = 0.0
def _collection_count(x):
if x is None:
return 0
try:
c = getattr(x, "Count", None)
if c is not None:
return int(c)
except Exception:
pass
try:
return len(x)
except Exception:
return 0
def _get_family_parameter_by_name(fm, name):
try:
for fp in fm.Parameters:
try:
if (fp.Definition.Name or "") == name:
return fp
except Exception:
pass
except Exception:
pass
return None
def _family_param_as_string(fm, fp):
if fp is None:
return None
try:
st = fp.StorageType
if st == StorageType.String:
return fm.CurrentType.AsString(fp)
if st == StorageType.Integer:
return str(fm.CurrentType.AsInteger(fp))
if st == StorageType.Double:
return str(fm.CurrentType.AsDouble(fp))
if st == StorageType.ElementId:
eid = fm.CurrentType.AsElementId(fp)
return "" if eid is None else str(eid.IntegerValue)
except Exception:
pass
return None
def _find_first_bindable_label_control(d):
elems = FilteredElementCollector(d).WhereElementIsNotElementType().ToElements()
for e in elems:
try:
p = e.LookupParameter("Label")
if p is None:
continue
if p.StorageType != StorageType.ElementId:
continue
if getattr(p, "IsReadOnly", False):
continue
return e
except Exception:
pass
return None
def _param_names_lower(e):
names = set()
try:
for p in e.Parameters:
try:
names.add((p.Definition.Name or "").strip().lower())
except Exception:
pass
except Exception:
pass
return names
def _score_labelish_text(e):
pn = _param_names_lower(e)
score = 0
if "label" in pn:
score += 200
if "sample text" in pn:
score += 120
if "horizontal align" in pn:
score += 40
if "vertical align" in pn:
score += 40
if "keep readable" in pn:
score += 20
return score
def _find_best_text_like_labelish(d):
elems = FilteredElementCollector(d).WhereElementIsNotElementType().ToElements()
best = None
best_score = 0
for e in elems:
try:
if not hasattr(e, "Text"):
continue
sc = _score_labelish_text(e)
if sc > best_score:
best_score = sc
best = e
except Exception:
pass
return best, best_score
if not bool(getattr(doc, "IsFamilyDocument", False)):
raise Exception("Open a Family Document in Revit (Family Editor) before running this node.")
fm = doc.FamilyManager
note_fp = _get_family_parameter_by_name(fm, TARGET_FAMILY_PARAM)
if note_fp is None:
raise Exception('Family parameter "{}" not found.'.format(TARGET_FAMILY_PARAM))
note_value = _family_param_as_string(fm, note_fp)
if note_value is None:
note_value = ""
copies = max(int(COPIES), 0)
dx_ft = float(DX_IN) / 12.0
dy_ft = float(DY_IN) / 12.0
mode = None
seed = None
seed_score = None
copy_ids = []
warnings = []
t = Transaction(doc, 'Update label (prefer bindable Label, else TextElement) + copy')
t.Start()
try:
# 1) Prefer true Label control: bind to Family parameter
label_control = _find_first_bindable_label_control(doc)
if label_control is not None:
lp = label_control.LookupParameter("Label")
lp.Set(note_fp.Id)
mode = "LabelControlBoundToFamilyParam"
seed = label_control
seed_score = None
else:
# 2) Fallback: TextElement-like
text_seed, sc = _find_best_text_like_labelish(doc)
if text_seed is None:
raise Exception(
'No bindable Label control found AND no suitable TextElement found to edit.'
)
text_seed.Text = note_value
mode = "TextElementTextSetFromFamilyParam"
seed = text_seed
seed_score = sc
# Copy for visual verification
for i in range(copies):
delta = XYZ(dx_ft * (i + 1), dy_ft * (i + 1), 0.0)
new_ids = ElementTransformUtils.CopyElement(doc, seed.Id, delta)
if _collection_count(new_ids) == 0:
warnings.append("CopyElement returned no ids on iteration {}".format(i + 1))
continue
for nid in new_ids:
copy_ids.append(nid.IntegerValue)
t.Commit()
except Exception:
try:
t.RollBack()
except Exception:
pass
raise
OUT = {
"mode": mode,
"seedId": seed.Id.IntegerValue,
"seedType": seed.GetType().FullName,
"seedScore": seed_score,
"familyParamName": TARGET_FAMILY_PARAM,
"familyParamId": note_fp.Id.IntegerValue,
"familyParamStorageType": int(note_fp.StorageType),
"valueUsed": note_value,
"copies": copies,
"dx_in": DX_IN,
"dy_in": DY_IN,
"copyIds": copy_ids,
"warnings": warnings,
}