Stack Bundle Icon Sizing

When using the stack bundle, will the icons resize when I only use 2 tools per stack or is the icon just two sizes, large and small?

I am making this 2x2 stack but the icons are small that you can barely make it out

I believe this is a limitation in the RevitAPI, it only has two sizes for buttons, so regardless of 2 or 3 items in a stack it will use the same small icon size of 16x16.

It can be “hacked” to display 24x24, but not officially supported in API.

https://forums.autodesk.com/t5/revit-api-forum/24x24-stackeditems/td-p/9168470

Thanks for sharing. At least I know now.

on a second note, i do notice that in a splitbutton it will show them at a larger size. Not sure if that could be a hack for pyrevit to explore.

image

fyi, for those curious, i was able to implement this using a startup.py at the root of the extension. here is my example that worked (currently had two pulldowns in stack). I know it is not officially supported by the API so if it ever gets removed by Autodesk, the startup.py can be deleted.

Before and After

imageimage

and use item.ShowText = False/True to show/hide the button names

# -*- coding: utf-8 -*-

import clr

clr.AddReference("AdWindows")

from Autodesk.Windows import ComponentManager, RibbonItemSize
from pyrevit import HOST_APP


# Target ribbon location:
# NXGN[Core].tab > Drawing Set.panel > SheetView.stack
# The actual runtime ribbon items we want to resize are the child controls
# inside that stack: "Sheets" and "Views".
TARGET_TAB_NAME = "NXGN[Core]"
TARGET_PANEL_NAME = "Drawing Set"
TARGET_ITEM_NAMES = ["Sheets", "Views"]

_done = False
_attempts = 0


# startup.py runs very early during pyRevit startup.
# In testing, this code was executing before the pyrevit extensions
# were fully populated, so a direct one-shot call from startup.py did not work.
#
# Using the Revit Idling event delays execution until the UI is far enough along
# that the target ribbon items actually exist.
#
# At the moment, one Idling pass has been enough, so MAX_ATTEMPTS is set to 1.
# If this ever becomes inconsistent on another machine or after a future update,
# this value can be increased.
MAX_ATTEMPTS = 1 


def safe_str(x):
    try:
        return str(x)
    except:
        try:
            return unicode(x)
        except:
            return ""

def walk_items(items):
    # This recursive walker allows the script to search through all nested children
    # under the target panel until it finds the specific controls to modify.
    # So far this has worked for .stack bundle.     
    if not items:
        return

    for item in items:
        yield item

        try:
            for child in walk_items(item.Items):
                yield child
        except:
            pass

        try:
            src = item.Source
            if src:
                for child in walk_items(src.Items):
                    yield child
        except:
            pass

def get_item_values(item):
    # The Autodesk ribbon objects can expose their identifying info in slightly
    # different places depending on the control type.
    #
    # To make matching more reliable, gather several possible identifying properties
    # from both the ribbon item itself and its Source object.    
    vals = []

    for prop in ["Text", "Title", "Name", "AutomationName", "Id"]:
        try:
            v = getattr(item, prop)
            if v:
                vals.append(safe_str(v))
        except:
            pass

    try:
        src = item.Source
        if src:
            for prop in ["Text", "Title", "Name", "AutomationName", "Id"]:
                try:
                    v = getattr(src, prop)
                    if v:
                        vals.append(safe_str(v))
                except:
                    pass
    except:
        pass

    return vals

def find_panel():
    # Find the runtime ribbon panel by tab title and panel title.
    # This is more reliable than trying to use the filesystem folder path directly,
    # since the actual UI must be found from the live Autodesk ribbon.    
    ribbon = ComponentManager.Ribbon
    if ribbon is None:
        return None

    for tab in ribbon.Tabs:
        if safe_str(tab.Title) != TARGET_TAB_NAME:
            continue

        for panel in tab.Panels:
            try:
                if safe_str(panel.Source.Title) == TARGET_PANEL_NAME:
                    return panel
            except:
                pass

    return None

def apply_hack():
    # This is the actual ribbon hack:
    # - find the target panel in the live ribbon
    # - locate the specific stacked child controls by name
    # - force them to display as Large instead of the normal small stacked size
    #
    # This is needed because standard stacked buttons in pyRevit/Revit display small,
    # and there is no normal pyRevit bundle setting to make those stacked items larger.    
    panel = find_panel()
    if panel is None:
        return False

    try:
        items = panel.Source.Items
    except:
        return False

    found_any = False

    for item in walk_items(items):
        vals = get_item_values(item)

        for target in TARGET_ITEM_NAMES:
            if target in vals:
                try:
                    # Force the stacked ribbon child item to use the larger display size.
                    item.Size = RibbonItemSize.Large
                except:
                    pass

                try:
                    item.ShowText = False # Hide or show button names
                except:
                    pass

                found_any = True
                break

    return found_any


def stop():
    # Always unsubscribe when finished so the Idling event does not keep firing.
    try:
        HOST_APP.uiapp.Idling -= on_idling
    except:
        pass


def on_idling(sender, args):
    global _done, _attempts

    if _done:
        # Safety check in case the handler fires again after the script is done.
        stop()
        return

    _attempts += 1

    try:
        # Try once the UI is idle.
        # This is the key part that makes the hack work at startup.        
        if apply_hack():
            _done = True
            stop()
            return
    except:
        _done = True
        stop()
        return
        
    # If the target items are still not available, stop after the allowed number
    # of attempts so the event does not remain subscribed indefinitely.
    if _attempts >= MAX_ATTEMPTS:
        _done = True
        stop()

# Cleanup first in case the script gets reloaded and an old handler is still attached.
try:
    stop()
except:
    pass

# Attach the deferred startup handler.
# This allows the ribbon hack to run after pyRevit has had time to build the tab/panel/items.
try:
    HOST_APP.uiapp.Idling += on_idling
except:
    pass