Lots of font styles the same. Trying to merge them together

This is my first attempt at pyRevit—actually my first attempt at any Revit scripting whatsoever. I’m decent with LISP, but this is new territory for me.

I have models that sometimes end up with literally hundreds of text types that are the same. I need to gather them up and merge them into one consistent type. The font name should be standardized using something like this.

3/32 arial opaque red box

If needed, the name can also include width, bold, italic, and underline, though that doesn’t come up very often.

All my attempts so far have either locked Revit up or resulted in a single text type being applied throughout the entire model.

As an FYI, once this is working I’ll need to do the same kind of consolidation for line styles, so a reusable or modular approach would be ideal.

What do you need from me to help move this forward?

Thank you
Lonnie

hi and welcome!

Just a tip (positive feedback): if you ask for help: show what you already have, why you struggle etc. People here also have a job who love to help you but not do the whole thing for you.

Seems you’re looking for the substitution of font types.

  1. replace all font types with a specific one.
  2. afterwards purge the unwanted ones.

I’d suggest you use SoFiSTiK BiMTOOLS for that. Has a tool that allows you to bulk change the font (including tags etc).

1 Like

OK here is what I am doing to lock up.
New file no template imperial.
3 pices of text named
Text Note 1
Text Note 2
Text Note 3
Only difference is text 2 and 3 are red.

Fair warning:
Hope this helps.

The script I’ve created locks up when it runs but here it is. If I run it as a test it says it’s going to change the 2 font styles to
3/32 arial opaque white unboxed
and
3/32 arial opaque red unboxed
while merging .
text note 3
into the red style.

When it runs it locks the pc.

Blockquote

-- coding: utf-8 --

Standardize TextNoteTypes to one font while preserving color variants.

Renames final canonical types like: “3/32 arial opaque red box”

FINAL VERSION — NO DRY RUN

from future import print_function
import clr

Revit API

clr.AddReference(“RevitAPI”)
from Autodesk.Revit.DB import (
FilteredElementCollector, Transaction, BuiltInParameter,
TextNoteType, TextNote, ElementId,
FailureProcessingResult, IFailuresPreprocessor
)

RevitServices

clr.AddReference(“RevitServices”)
from RevitServices.Persistence import DocumentManager

--------------------------

CONFIG

--------------------------

TARGET_FONT = “Arial”
WIDTH_ROUND = 4

--------------------------

Revit doc

--------------------------

uiapp = revit
uidoc = uiapp.ActiveUIDocument
doc = uidoc.Document
if not uidoc:
raise Exception(“Open a project first.”)

--------------------------

Failures

--------------------------

class SwallowFailures(IFailuresPreprocessor):
def PreprocessFailures(self, fa):
return FailureProcessingResult.Continue

def start_tx(name):
tx = Transaction(doc, name)
tx.Start()
opts = tx.GetFailureHandlingOptions()
opts.SetFailuresPreprocessor(SwallowFailures())
tx.SetFailureHandlingOptions(opts)
return tx

--------------------------

Name accessor

--------------------------

def get_type_name(t):
try:
p = t.get_Parameter(BuiltInParameter.SYMBOL_NAME_PARAM)
return p.AsString() or “”
except:
return “”

--------------------------

Helpers

--------------------------

def pval(t, bip, kind):
p = t.get_Parameter(bip)
if not p: return None
try:
if kind == “str”: return (p.AsString() or “”).strip().lower()
if kind == “int”: return p.AsInteger()
if kind == “dbl”:
v = p.AsDouble()
return None if v is None else round(v, 8)
except:
return None
return None

gcd for fractional inches (IronPython-safe)

def _gcd(a, b):
a = abs(int(a)); b = abs(int(b))
while b:
a, b = b, a % b
return a if a else 1

def format_fractional_inches(size_ft):
if size_ft is None:
return “”
inches = float(size_ft) * 12.0
num = int(round(inches * 256))
den = 256
g = _gcd(num, den)
num //= g
den //= g
whole = num // den
rem = num % den
if whole and rem:
return “{} {}/{}”.format(whole, rem, den)
elif whole and not rem:
return str(whole)
else:
return “{}/{}”.format(rem, den)

decode 0x00BBGGRR into (r,g,b)

def decode_color_int(val):
if val is None:
return (0,0,0)
r = val & 0xFF
g = (val >> 8) & 0xFF
b = (val >> 16) & 0xFF
return (r,g,b)

def color_name_or_rgb(rgb):
r,g,b = rgb
known = {
(255, 0, 0): “red”,
(255, 255, 255): “white”,
(0, 0, 255): “blue”,
(0, 0, 0): “black”,
(0, 255, 0): “green”,
(128, 128, 128): “gray”,
}
return known.get((r,g,b), “rgb({},{},{})”.format(r,g,b)).lower()

Find proper frame visibility BIP

FRAME_BIP = getattr(BuiltInParameter, “TEXT_FRAME_VISIBLE”, None)
if FRAME_BIP is None:
FRAME_BIP = getattr(BuiltInParameter, “TEXT_BOX_VISIBLE”, None)

def width_value(t):
v = pval(t, BuiltInParameter.TEXT_WIDTH_SCALE, “dbl”)
return None if v is None else round(v, WIDTH_ROUND)

--------------------------

Signature (font excluded, color included)

--------------------------

def make_signature(t):
return (
pval(t, BuiltInParameter.TEXT_SIZE, “dbl”),
pval(t, BuiltInParameter.TEXT_BACKGROUND, “int”),
pval(t, BuiltInParameter.LINE_COLOR, “int”),
pval(t, FRAME_BIP, “int”),
pval(t, BuiltInParameter.LINE_PEN, “int”),
width_value(t),
)

--------------------------

Naming per your spec:

3/32 arial opaque red box

--------------------------

def name_from_type(t):
size_ft = pval(t, BuiltInParameter.TEXT_SIZE, “dbl”)
size_str = format_fractional_inches(size_ft)

font_raw = pval(t, BuiltInParameter.TEXT_FONT, "str")
font_str = (font_raw or TARGET_FONT).lower()

bg_val = pval(t, BuiltInParameter.TEXT_BACKGROUND, "int")
bg_str = "opaque" if bg_val == 0 else "transparent"

rgb = decode_color_int(pval(t, BuiltInParameter.LINE_COLOR, "int"))
color_str = color_name_or_rgb(rgb)

frame_val = pval(t, FRAME_BIP, "int")
box_str = "box" if frame_val == 1 else "no box"

return "{} {} {} {} {}".format(size_str, font_str, bg_str, color_str, box_str)

def safe_rename_type(t, desired_name):
base = desired_name
name = base
suffix = 1
while True:
try:
p = t.get_Parameter(BuiltInParameter.SYMBOL_NAME_PARAM)
if p and p.AsString() != name:
p.Set(name)
return name
except:
suffix += 1
name = “{} ({})”.format(base, suffix)

--------------------------

Collect types + notes once

--------------------------

ALL_TYPES = list(FilteredElementCollector(doc).OfClass(TextNoteType))
ALL_NOTES = list(FilteredElementCollector(doc).OfClass(TextNote))

--------------------------

MAIN

--------------------------

def merge_text_types():

# Group by font-free signature
groups = {}
for t in ALL_TYPES:
    sig = make_signature(t)
    groups.setdefault(sig, []).append(t)

plan = []

for sig, items in groups.items():

    # Find existing canonical with TARGET_FONT
    canonical = None
    for t in items:
        f = pval(t, BuiltInParameter.TEXT_FONT, "str")
        if f == TARGET_FONT.lower():
            canonical = t.Id
            break

    # Otherwise duplicate one
    if not canonical:
        base = items[0]
        base_name = get_type_name(base) or "text"
        try:
            new_id = base.Duplicate(base_name + " (std)")
        except:
            new_id = base.Duplicate("std clone")
        new_t = doc.GetElement(new_id)
        pf = new_t.get_Parameter(BuiltInParameter.TEXT_FONT)
        if pf:
            pf.Set(TARGET_FONT)
        canonical = new_id

    # These are TRUE duplicates of same color group
    dups = [t.Id for t in items if t.Id != canonical]

    plan.append((sig, canonical, dups))

# APPLY CHANGES
tx = start_tx("Standardize Text Types (No DryRun)")
try:
    # 1) Retype notes
    dup_to_canon = {}
    for _, canon, dlist in plan:
        for d in dlist:
            dup_to_canon[d.IntegerValue] = canon

    for n in ALL_NOTES:
        tid = n.GetTypeId().IntegerValue
        if tid in dup_to_canon:
            n.ChangeTypeId(dup_to_canon[tid])

    # 2) Delete true duplicates
    for _, _, dlist in plan:
        for d in dlist:
            try:
                doc.Delete(d)
            except:
                pass

    # 3) Rename canonical types
    for _, canon, _ in plan:
        t = doc.GetElement(canon)
        desired = name_from_type(t)
        safe_rename_type(t, desired)

    tx.Commit()
    print("✓ Complete: font standardized, color preserved, types renamed.")

except Exception as ex:
    tx.RollBack()
    print("ERROR:", ex)
    raise

--------------------------

Entry (NO DRY RUN)

--------------------------

if name == “main”:
merge_text_types()

Blockquote