Building comments dynamo- Uses fixed parameters to store building comments and record replies and statuses. I fear there is no ‘legit’ way in the API to fill in place holders. We do have ideate- which can read and write the key schedule but cannot save the key schedule definition
which I am chatting with them about now.
The attached DYN
View+Revision-Schedule-Building-Dept-Comments-Create.dyn (69.8 KB)
Uses the dictionary value at key and a slider to select the category to store using Forge_ID. (could be a DYF)
This then binds in the shared params to the Key Schedule.
These get passed to the Check.Key.Create which creates the Key Schedules if it can find the params and associated with revisions (cat = Category.GetCategory(doc, BuiltInCategory.OST_RevisionClouds)
then I wanted to populate some key values in the DYN with the output of the create node and I cannot populate the values.![]()
![]()
Each run creates a new line but reporting it cannot modify the content.
Just need to verify that is indeed the case- if not is there a work around?
Py.KeySched.Add.Populate:
# Dynamo Player-safe Key Schedule row adder with duplicate check and built-in Yes/No handling
# IN[0]=schedule_name (str) # exact schedule view name
# IN[1]=param_names (list[str]) # headers OR field/parameter names; "Key Name" optional
# IN[2]=rows (list[list[Any]]) # each inner list aligns to param_names order
# IN[3]=case_sensitive (bool, optional) # default False
# IN[4]=trim_whitespace (bool, optional) # default True
# OUT: dict with ok, message, scheduleId, isKeySchedule, results, insertedRowIndices
from typing import List, Tuple
import clr
clr.AddReference("RevitAPI")
clr.AddReference("RevitServices")
from Autodesk.Revit.DB import (
FilteredElementCollector, ViewSchedule, SectionType, TableSectionData,
LabelUtils, BuiltInParameter, ElementId, ParameterElement
)
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
# -------- inputs --------
schedule_name = IN[0] if len(IN) > 0 else None
param_names: List[str] = IN[1] if len(IN) > 1 and IN[1] else []
rows_in: List[List[object]] = IN[2] if len(IN) > 2 and IN[2] else []
case_sensitive = bool(IN[3]) if len(IN) > 3 and IN[3] is not None else False
trim_whitespace = True if len(IN) <= 4 or IN[4] is None else bool(IN[4])
# -------- helpers --------
def _safe_str(v):
try:
return "" if v is None else str(v)
except:
return ""
def _norm(s):
if s is None:
s = ""
if trim_whitespace:
s = s.strip()
if not case_sensitive:
s = s.lower()
return s
def _to_cell_text(v):
# Convert values to display strings that Revit table accepts
if isinstance(v, bool):
return "Yes" if v else "No"
if isinstance(v, (int, float)):
# numbers become their string form
return str(v)
if isinstance(v, str):
low = v.strip().lower()
if low in ["true", "t", "1"]: # accept truthy strings
return "Yes"
if low in ["false", "f", "0"]:
return "No"
return v
return _safe_str(v)
def _get_schedule_by_name(name):
if not name:
return None
for vs in FilteredElementCollector(doc).OfClass(ViewSchedule):
if vs.Name == name:
return vs
return None
def _build_header_map(vs):
"""
Returns (name_to_col, body, header, key_col_idx).
Accepts for each column:
- header text, field name, column heading override,
- schedulable field name, ParameterElement.Name,
- BuiltInParameter label. Also detects 'Key Name'.
"""
table = vs.GetTableData()
body = table.GetSectionData(SectionType.Body)
header = table.GetSectionData(SectionType.Header)
sd = vs.Definition
visible_fields = []
try:
for i in range(sd.GetFieldCount()):
f = sd.GetField(i)
if not f.IsHidden:
visible_fields.append(f)
except:
pass
name_to_col = {}
key_col_idx = None
ncols = body.NumberOfColumns
for c in range(ncols):
f = visible_fields[c] if c < len(visible_fields) else None
keys = set()
# header cell
try:
ht = header.GetCellText(0, c)
if ht:
keys.add(_norm(ht))
if _norm(ht) == _norm("Key Name"):
key_col_idx = c
except:
pass
# field and overrides
if f:
try:
fn = f.GetName()
if fn:
keys.add(_norm(fn))
except:
pass
try:
ch = getattr(f, "ColumnHeading", None) or f.GetColumnHeading()
if ch:
keys.add(_norm(ch))
if _norm(ch) == _norm("Key Name"):
key_col_idx = c
except:
pass
try:
sf = f.GetSchedulableField()
if sf:
try:
sfn = sf.GetName()
if sfn:
keys.add(_norm(sfn))
if _norm(sfn) == _norm("Key Name"):
key_col_idx = c
except:
pass
try:
pid = sf.ParameterId
if isinstance(pid, ElementId) and pid.IntegerValue > 0:
pe = doc.GetElement(pid)
if isinstance(pe, ParameterElement) and pe.Name:
keys.add(_norm(pe.Name))
if _norm(pe.Name) == _norm("Key Name"):
key_col_idx = c
except:
pass
try:
pid = sf.ParameterId
if isinstance(pid, ElementId) and pid.IntegerValue < 0:
bip = BuiltInParameter(pid.IntegerValue)
label = LabelUtils.GetLabelFor(bip)
if label:
keys.add(_norm(label))
if _norm(label) == _norm("Key Name"):
key_col_idx = c
except:
pass
except:
pass
for k in keys:
if k and k not in name_to_col:
name_to_col[k] = c
# fallback: assume column 0 is Key Name if not found
if key_col_idx is None and ncols > 0:
key_col_idx = 0
return name_to_col, body, header, key_col_idx
def _read_row_signature(body, row_idx, cols):
vals = []
for c in cols:
try:
vals.append(_norm(body.GetCellText(row_idx, c)))
except:
vals.append(_norm(""))
return tuple(vals)
# -------- main --------
results, inserted_indices = [], []
ok = True
msgs = []
schedule_id = None
is_key = None
if not schedule_name:
ok = False
msgs.append("No schedule name provided.")
else:
vs = _get_schedule_by_name(schedule_name)
if not vs:
ok = False
msgs.append("Schedule not found: '{}'".format(schedule_name))
else:
schedule_id = vs.Id.IntegerValue
try:
is_key = bool(vs.Definition.IsKeySchedule)
except:
is_key = False
if not is_key:
ok = False
msgs.append("'{}' is not a Key Schedule.".format(schedule_name))
elif not param_names:
ok = False
msgs.append("No parameter (column) names supplied.")
else:
name_to_col, body, header, key_col = _build_header_map(vs)
# Ensure "Key Name" is part of our mapping; if not provided, we will infer from first value
norm_param_names = [_norm(p or "") for p in param_names]
user_has_keyname = _norm("Key Name") in norm_param_names
if not user_has_keyname:
norm_param_names = [_norm("Key Name")] + norm_param_names
prepend_key = True
else:
prepend_key = False
# Validate columns exist
missing = [("Key Name" if p == _norm("Key Name") else param_names[i - (0 if user_has_keyname else 1)])
for i, p in enumerate(norm_param_names) if p not in name_to_col]
if missing:
ok = False
msgs.append("Missing schedule columns: " + ", ".join(missing))
else:
cols = [name_to_col[p] for p in norm_param_names]
# Existing signatures (including Key Name)
existing = {}
try:
first, last = body.FirstRowNumber, body.LastRowNumber
except:
first, last = 0, -1
if last >= first:
for r in range(first, last + 1):
sig = _read_row_signature(body, r, cols)
if sig not in existing:
existing[sig] = r
# Plan inserts with duplicate check
to_insert = []
for r in rows_in:
row_list = list(r) if isinstance(r, (list, tuple)) else [r]
# Determine a full row vector aligned with norm_param_names (maybe adding Key Name)
if prepend_key:
if not row_list:
results.append({"row": row_list, "status": "Skipped: no Key Name", "rowIndex": None})
continue
key_text = _to_cell_text(row_list[0])
row_for_sig = [key_text] + row_list
else:
# user provided "Key Name" among param_names
idx_key = param_names.index("Key Name") if "Key Name" in param_names else None
if idx_key is None or idx_key >= len(row_list) or row_list[idx_key] in [None, ""]:
results.append({"row": row_list, "status": "Skipped: no Key Name", "rowIndex": None})
continue
row_for_sig = list(row_list)
# Build signature
norm_vals = []
for i in range(len(norm_param_names)):
v = row_for_sig[i] if i < len(row_for_sig) else None
norm_vals.append(_norm(_to_cell_text(v)))
sig = tuple(norm_vals)
if sig in existing:
results.append({"row": row_list, "status": "Already present", "rowIndex": existing[sig]})
else:
to_insert.append((row_list, row_for_sig, sig))
# Insert in one transaction
if to_insert:
TransactionManager.Instance.EnsureInTransaction(doc)
try:
for row_list, row_for_sig, sig in to_insert:
insert_at = body.LastRowNumber + 1
body.InsertRow(insert_at)
for i in range(len(norm_param_names)):
if i >= len(row_for_sig):
break
text = _to_cell_text(row_for_sig[i])
body.SetCellText(insert_at, cols[i], text)
inserted_indices.append(insert_at)
existing[sig] = insert_at
results.append({"row": row_list, "status": "Inserted", "rowIndex": insert_at})
except Exception as ex:
ok = False
msgs.append("Insert failed: {}".format(_safe_str(ex)))
finally:
TransactionManager.Instance.TransactionTaskDone()
else:
msgs.append("No new rows to insert (all already present).")
OUT = {
"ok": ok,
"message": "; ".join(msgs) if msgs else "Completed.",
"scheduleId": schedule_id,
"isKeySchedule": is_key,
"results": results,
"insertedRowIndices": inserted_indices
}


