# Generic Annotations Create Label and Link to Family Parameter

**URL:** <https://forum.dynamobim.com/t/generic-annotations-create-label-and-link-to-family-parameter/113678>\
**Category:** Revit\
**Tags:** family, cpython3\
**Created:** [January 15, 2026, 8:41am UTC](https://forum.dynamobim.com/t/generic-annotations-create-label-and-link-to-family-parameter/113678 "2026-01-15T08:41:06Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![GallowayUS.com\_RonAl](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.dynamobim.com/gallowayus.com_ronal/32/196301_2.png) [@GallowayUS.com\_RonAl](https://forum.dynamobim.com/u/GallowayUS.com_RonAl)\
**Post date:** [January 15, 2026, 8:41am UTC](https://forum.dynamobim.com/t/generic-annotations-create-label-and-link-to-family-parameter/113678/1 "2026-01-15T08:41:06Z")

</div>

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](https://forum.dynamobim.com/uploads/short-url/tcc0E8vRBof1rCSxAFbrQZLahVE.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,`  
`}`

---

<div class="post-metadata">

**Author:** ![SeanP](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.dynamobim.com/seanp/32/42560_2.png) [@SeanP](https://forum.dynamobim.com/u/SeanP)\
**Post date:** [January 15, 2026, 10:14am UTC](https://forum.dynamobim.com/t/generic-annotations-create-label-and-link-to-family-parameter/113678/2 "2026-01-15T10:14:07Z")

</div>

As far as I know, and based on the last time I looked into doing this, the Label is not exposed in the API and therefore you cannot modify.

---

<div class="post-metadata">

**Author:** ![jacob.small](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.dynamobim.com/jacob.small/32/161030_2.png) [@jacob.small](https://forum.dynamobim.com/u/jacob.small)\
**Post date:** [January 15, 2026, 10:31pm UTC](https://forum.dynamobim.com/t/generic-annotations-create-label-and-link-to-family-parameter/113678/3 "2026-01-15T22:31:53Z")

</div>

> [@SeanP](#):
>
> based on the last time I looked into doing this, the Label is not exposed in the API and therefore you cannot modify

This matches my recollection as well. Depending on the what is necessitating the need there may be a functional work-arounds (copy from a seed file being quite valid and honestly might be a better solution than making it anew for many situations).
