MoveTitleBlock.py
# purpose and IO: IN[0]=run (bool, optional), IN[1]=sheet Ids (list/int/ElementId or None), OUT=report list of dicts
# Moves all paper-space elements so the largest title block's bounding-box Min becomes (0,0) per sheet.
# Skips CAD Import/Link instances named like "0,0,0" or "0,0-Origin". One transaction across all sheets.
# Note: To avoid CPython generic-binding issues, we try batch MoveElements; on failure we fall back to per-element MoveElement.
import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
from Autodesk.Revit.DB import (
FilteredElementCollector, ViewSheet, FamilyInstance, BuiltInCategory,
ImportInstance, ElementTransformUtils, XYZ, ElementId
)
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Optional: if batch move works in your CPython, you can enable the List+XYZ path below.
try:
from System.Collections.Generic import List as ClrList
HAS_CLR_LIST = True
except:
HAS_CLR_LIST = False
doc = DocumentManager.Instance.CurrentDBDocument
# ---------------- inputs ----------------
run = True if (len(IN) == 0 or IN[0] in (True, None)) else False
input_ids = IN[1] if len(IN) > 1 else None
def _to_elemid(e):
if isinstance(e, ElementId): return e
try: return ElementId(int(e))
except: return None
target_sheet_ids = None
if input_ids:
ids = []
seq = input_ids if isinstance(input_ids, (list, tuple)) else [input_ids]
for x in seq:
eid = _to_elemid(x)
if eid: ids.append(eid)
if ids: target_sheet_ids = set(ids)
# ---------------- helpers ----------------
def tblocks_on_sheet(sheet):
return list(
FilteredElementCollector(doc, sheet.Id)
.OfCategory(BuiltInCategory.OST_TitleBlocks)
.OfClass(FamilyInstance)
.WhereElementIsNotElementType()
)
def bbox_area_in_view(elem, view):
bb = elem.get_BoundingBox(view)
if not bb: return 0.0
return abs((bb.Max.X - bb.Min.X) * (bb.Max.Y - bb.Min.Y))
def largest_titleblock(sheet):
tbs = tblocks_on_sheet(sheet)
if not tbs: return None, None
ranked = [(bbox_area_in_view(tb, sheet), tb) for tb in tbs]
ranked.sort(key=lambda x:x[0], reverse=True)
tb = ranked[0][1]
bb = tb.get_BoundingBox(sheet)
return tb, bb
def is_origin_cad_import(elem):
# True for ImportInstance whose instance/type name includes 0,0,0 or 0,0-origin (spaces ignored)
if not isinstance(elem, ImportInstance):
return False
name = ""
try:
name = elem.Name or ""
except:
name = ""
if not name:
try:
sym = doc.GetElement(elem.GetTypeId())
name = sym.Name if sym else ""
except:
name = ""
s = name.lower().replace(" ", "")
return ("0,0,0" in s) or ("0,0-origin" in s) or ("00,0-origin" in s)
def paper_elems(sheet):
# All view-owned (paper-space) elements
return list(FilteredElementCollector(doc, sheet.Id).WhereElementIsNotElementType())
# ---------------- main ----------------
report = []
if not run:
OUT = ["Set IN[0]=True to run."]
else:
sheets = list(FilteredElementCollector(doc).OfClass(ViewSheet))
if target_sheet_ids:
sheets = [s for s in sheets if s.Id in target_sheet_ids]
TransactionManager.Instance.EnsureInTransaction(doc)
for s in sheets:
tb, tb_bb = largest_titleblock(s)
if not tb or not tb_bb:
report.append({"sheet": s.SheetNumber, "name": s.Name, "status": "no title block"})
continue
move = XYZ(-tb_bb.Min.X, -tb_bb.Min.Y, 0.0)
if move.IsZeroLength():
report.append({"sheet": s.SheetNumber, "name": s.Name, "status": "already at (0,0)"})
continue
all_elems = paper_elems(s)
elems = [e for e in all_elems if not is_origin_cad_import(e)]
skipped = len(all_elems) - len(elems)
if not elems:
report.append({"sheet": s.SheetNumber, "name": s.Name, "status": "no movable paper elements"})
continue
# Unpin movables
pinned = []
for e in elems:
try:
if getattr(e, "Pinned", False):
e.Pinned = False
pinned.append(e)
except:
pass
moved = 0
tried_batch = False
batch_ok = False
# Try batch (works in some CPython setups)
if HAS_CLR_LIST:
try:
id_list = ClrList[ElementId]()
for e in elems: id_list.Add(e.Id)
# Prefer XYZ overload; CPython should bind to ICollection[ElementId], XYZ
ElementTransformUtils.MoveElements(doc, id_list, move)
moved = len(elems)
tried_batch = True
batch_ok = True
except:
batch_ok = False
# Fallback: move per element (robust in CPython; still one transaction)
if not batch_ok:
for e in elems:
try:
ElementTransformUtils.MoveElement(doc, e.Id, move)
moved += 1
except:
pass
# Restore pins
for e in pinned:
try: e.Pinned = True
except: pass
report.append({
"sheet": s.SheetNumber,
"name": s.Name,
"status": "moved",
"moved_count": moved,
"skipped_origin_cad": skipped,
"batch_tried": tried_batch,
"batch_ok": batch_ok,
"delta": (round(move.X,6), round(move.Y,6), 0.0)
})
TransactionManager.Instance.TransactionTaskDone()
OUT = report
Comments
Post a Comment