From eee834ed7d2fc397124d50969edc1fa94465b9ac Mon Sep 17 00:00:00 2001 From: Chris Crosley Date: Mon, 6 Jul 2026 20:53:17 -0400 Subject: [PATCH 1/2] fix(loader): reload SmartButton icons, revise Update.SmartButton logic. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On reload, UpdateExistingChildren in PulldownButtonBuilder and SplitButtonBuilder re-applied bundle icons via ButtonPostProcessor.Process but never re-ran __selfinit__. SmartButtons nested in pulldowns and split buttons therefore lost any icon/state set by their init scripts, and icons set in a previous session could linger out of sync with current state (e.g. the Update button staying orange after updating). Top-level, slide-out, and stacked SmartButtons were unaffected — PushButtonBuilder and StackBuilder already re-ran __selfinit__ on their update paths. Note: this was originally reported as a stale IconManager BitmapCache, but the cache is not the cause — IconManager (and its cache) is recreated on every LoadSession, so no bitmaps survive a reload. Also: - Update.smartbutton: a failed update-availability check no longer deactivates the button (it only skips the icon swap and logs at debug); only the user_can_update gate returns False. Previously any transient git/network error during load disabled the one tool that performs the update. - Add "Test Reload Icon Toggle" SmartButton to pyRevitDevTools Bundle Tests pulldown: clicking toggles a state persisted in the user config ini; __selfinit__ re-applies the matching icon on load. After a reload the icon must match the stored state, distinguishing "__selfinit__ not re-run" from "bundle icon not restored". --- .../Buttons/PulldownButtonBuilder.cs | 12 +++++ .../UIManager/Buttons/SplitButtonBuilder.cs | 12 +++++ .../Update.smartbutton/script.py | 10 ++-- .../bundle.yaml | 11 +++++ .../icon.dark.png | Bin 0 -> 1174 bytes .../icon.png | Bin 0 -> 1818 bytes .../off.dark.png | Bin 0 -> 1174 bytes .../off.png | Bin 0 -> 1818 bytes .../on.png | Bin 0 -> 1700 bytes .../script.py | 45 ++++++++++++++++++ .../Bundle Tests.pulldown/bundle.yaml | 1 + 11 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/bundle.yaml create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/icon.dark.png create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/icon.png create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/off.dark.png create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/off.png create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/on.png create mode 100644 extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/script.py diff --git a/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/PulldownButtonBuilder.cs b/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/PulldownButtonBuilder.cs index b6414847b4..8a8c219ba1 100644 --- a/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/PulldownButtonBuilder.cs +++ b/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/PulldownButtonBuilder.cs @@ -391,6 +391,18 @@ private void UpdateExistingChildren( existingBtn.Enabled = true; existingBtn.Visible = true; + // Handle SmartButton-specific initialization on update; + // __selfinit__ must re-run after post-processing since it may override the bundle icon + if (sub.Type == CommandComponentType.SmartButton && _smartButtonScriptInitializer != null) + { + var shouldActivate = _smartButtonScriptInitializer.ExecuteSelfInit(sub, existingBtn); + if (!shouldActivate) + { + existingBtn.Enabled = false; + Logger.Debug($"SmartButton '{sub.DisplayName}' in pulldown deactivated by __selfinit__ during update."); + } + } + Logger.Debug($"Updated existing child button '{sub.DisplayName}' in pulldown '{component.DisplayName}'. New text: '{buttonText}'"); } catch (Exception ex) diff --git a/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/SplitButtonBuilder.cs b/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/SplitButtonBuilder.cs index 1563273a7b..e6f1f979d1 100644 --- a/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/SplitButtonBuilder.cs +++ b/dev/pyRevitLoader/pyRevitAssemblyBuilder/UIManager/Buttons/SplitButtonBuilder.cs @@ -465,6 +465,18 @@ private void UpdateExistingChildren( if (!wasVisible && IsRibbonItemVisible(existingBtn, fallbackVisible: false)) newlyVisibleNames.Add(sub.DisplayName); + // Handle SmartButton-specific initialization on update; + // __selfinit__ must re-run after post-processing since it may override the bundle icon + if (sub.Type == CommandComponentType.SmartButton && _smartButtonScriptInitializer != null) + { + var shouldActivate = _smartButtonScriptInitializer.ExecuteSelfInit(sub, existingBtn); + if (!shouldActivate) + { + existingBtn.Enabled = false; + Logger.Debug($"SmartButton '{sub.DisplayName}' in split button deactivated by __selfinit__ during update."); + } + } + Logger.Debug($"Updated existing child button '{sub.DisplayName}' in split button '{component.DisplayName}'. New text: '{buttonText}'"); } catch (Exception ex) diff --git a/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py b/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py index 717526658c..b46b4e8b48 100644 --- a/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py +++ b/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py @@ -14,18 +14,18 @@ def __selfinit__(script_cmp, ui_button_cmp, __rvt__): # do not load the tool if user should not update if not user_config.user_can_update: return False - # otherwise set icon depending on if updates are available + # set icon if updates available; a failed availability check must not + # deactivate the tool that performs the update try: - has_update_icon = script_cmp.get_bundle_file('icon_hasupdates.png') if user_config.check_updates \ and not user_config.auto_update \ and updater.check_for_updates(): + has_update_icon = script_cmp.get_bundle_file('icon_hasupdates.png') ui_button_cmp.set_icon(has_update_icon, icon_size=ribbon.ICON_LARGE) - return True except Exception as ex: - logger.error('Error in checking updates: {}'.format(ex)) - return False + logger.debug('Error in checking updates: %s', ex) + return True if __name__ == '__main__': diff --git a/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/bundle.yaml b/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/bundle.yaml new file mode 100644 index 0000000000..a4e8baf34e --- /dev/null +++ b/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/bundle.yaml @@ -0,0 +1,11 @@ +title: >- + Test Reload + + Icon Toggle +tooltip: >- + SmartButton that mirrors Update.smartbutton: click to toggle a state + stored in the user config ini; __selfinit__ applies the orange dot icon + when the state is on and leaves the default bundle icon when off. After a + reload the icon must match the stored state - default shown while on + means __selfinit__ was not re-run in this pulldown, orange shown while + off means the loader failed to restore the bundle icon. diff --git a/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/icon.dark.png b/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/icon.dark.png new file mode 100644 index 0000000000000000000000000000000000000000..dc6b0c4d0fd9e3f167538f69fa00ddc21af1a5e0 GIT binary patch literal 1174 zcmV;H1Zn$;P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1S?5IK~#8N?VaCi zS7jW>x9PGXVGv}H6o_|03WkUdgsF&h-7m|@po?5FFS_W$8*kF7RDXwxIDZf%7z9Z- ziBLfqOuaFxuiDRxW%lFs!B2dT$LFh?+jGuyp7Vae3&z;?d4E0|=X<`-IeSi@5JCtc zgb+dqA%qYq(CKs*<1p^R5Ah@X7N^m;;Trb2Z_vKx1AG(L<6{4DVm2_aCvhB4;IH`a zJpBcaqx}+}@G_zZgIR*x(f)e>!q6ZWLi^dL@fBR+<=YEG7{a}1ga#HX1JBWiGI}d2+ zS^OG9O{eI2TJ68`inzGk%*xP6(G+?KDe|ker)Uvz4k1`0lnIW4x$SDVFhA5VNJw!2VV-gex_6CaA5DixQV z&T$`4TqtC>3%2>(zn4E_{99WnvZhc}-sx%|Q#>g0MyaUmI_6`F2SpB*ips8&KBjn3 z>B@tm?CYs>h+*r@G-@MB3DX9W!FnSrg%`qmS980CHR=)L5(Kg=@#1=A5%Oi zlH)x;3uPt003CvQTgf3vFT%Z@_A$kSB03H2Z6&9nQ>CJ^>oFfwJSZ|*Dk{6a=VOWo zMRYB=xACtHQ$)+ry{%+9dH}B#iptymg#A9IxKPOUUI>L~vqyY9@u8S5%HM6J7v+~> z%uO|#rmc@X?&GNj3fitCp)hT9%EwY26n(W6qRqbOW2p{`+TIUZ7wU8_V?DcaQFx*d zq^-W?-ic9p(4p og%CmrA%qY@2qA>HCw+bY0njb%3a|(BWB>pF07*qoM6N<$f+kHdM*si- literal 0 HcmV?d00001 diff --git a/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/icon.png b/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5da357996a3103efe03c53619c320bf99d2728de GIT binary patch literal 1818 zcmV+#2j%#QP)(QBEOxLg8c}IyiIe z^pW1)-oh0BD_5lC3edb}^XCY|Z>z8xXaTN`X!#Mys|pA6#qfo}gPrH1AEi4=q5$*T zwpP!UkG>DtY8naS`Gl!t)NBpm*I zgkKh}37$N*f9HjSWB-RKDL^)xtvWSY^Ps@v3ZI>dWB4y}R^dnWwU@T<-MhD#c=V(L zHm%vP3JsnH8j_BE72cANhx>XuUP>}(Vg+byd|;Z)8UGo=BS4T;Tth;HpX=G_KkV<{ zo=+lRA_Zt(y=kEa`aG~OiFnbGTo@MD<$89%9ebp>12ng_uLRkra9!N79P^4?ieVFHkQZ)#Y2;>^&%ao6U~uK`VM8{2?CP~N}%Phv7uy(ib* z+2hjGX#twQ^}Tux3prr6OW#sK;GYF4EPLa?Z~x}X)7eFMMK)WbVPQWNntT9->oS^o zVSd}zYFD1l3XmV0{!?I)E5A}jp|Oq&KX&EkuA@mLs5+) zkAV4YTdM=)*Q6);00R1(#>NLCPc$MIU^W*Xrt}`HCBYY&Gx1C6+Wz&NqjEl~`tN5G0~wkAJ1;}qx(Q4XL2 zc(3+yP~Y3TbKFL3Jpu}2GuHd$12A{AM!#z-wl08%+HR|@KsMM4txiFWcW=B|3Y?;J z-)tpKSVi%c*Sq$dHWgYWFeq}Lt+ql5Lan;TN@ybrm2cSSDj5}7Wur4LKub$YM&K@+ zV3m!wWV2c0_aDXuxH$8cC8T@qe*sDZnfXO*&BX+}ISL)J}}j#fb)F^b`F?|%uhj2JQ0HY#aLJ2 zh)6G>raG&4!XnyhqpN5_yPVi{MP%~N0VTTy%oH#wd7ifsS{2~+7oR82S)+6v zdAY;tx@h0q6UrWIZH1wzbuR2%6VF%w4sf+XpBT+B(r{xLLo z=EjD_bs#HT_?14ot-rg|$u!(qkALl@cOL~JFI+Bl1YSA1{PW*;=;scikotibcSmAC6m zAA9YYL|QNtPsAOoH!KMy_!YvJla75A4u#4V7mpWANPLEJq-XcQyjhpO0&Ep1fv;J} zSw*(YtG&E3>Ey!{p9&Ucv)9y&&RDPLV*SvSMDgBcuiF2|4gh>hU zKOgb=DzFlm6U9&9T&VOC=&ShxzxN;UF+-YHZ@L}PWdtl#<#u2JAK%#h`wu=|G%|v4 z5#^++9uFiqPROyoo?UNZ(Xr<7csw4D$K&yMJRXn7Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1S?5IK~#8N?VaCi zS7jW>x9PGXVGv}H6o_|03WkUdgsF&h-7m|@po?5FFS_W$8*kF7RDXwxIDZf%7z9Z- ziBLfqOuaFxuiDRxW%lFs!B2dT$LFh?+jGuyp7Vae3&z;?d4E0|=X<`-IeSi@5JCtc zgb+dqA%qYq(CKs*<1p^R5Ah@X7N^m;;Trb2Z_vKx1AG(L<6{4DVm2_aCvhB4;IH`a zJpBcaqx}+}@G_zZgIR*x(f)e>!q6ZWLi^dL@fBR+<=YEG7{a}1ga#HX1JBWiGI}d2+ zS^OG9O{eI2TJ68`inzGk%*xP6(G+?KDe|ker)Uvz4k1`0lnIW4x$SDVFhA5VNJw!2VV-gex_6CaA5DixQV z&T$`4TqtC>3%2>(zn4E_{99WnvZhc}-sx%|Q#>g0MyaUmI_6`F2SpB*ips8&KBjn3 z>B@tm?CYs>h+*r@G-@MB3DX9W!FnSrg%`qmS980CHR=)L5(Kg=@#1=A5%Oi zlH)x;3uPt003CvQTgf3vFT%Z@_A$kSB03H2Z6&9nQ>CJ^>oFfwJSZ|*Dk{6a=VOWo zMRYB=xACtHQ$)+ry{%+9dH}B#iptymg#A9IxKPOUUI>L~vqyY9@u8S5%HM6J7v+~> z%uO|#rmc@X?&GNj3fitCp)hT9%EwY26n(W6qRqbOW2p{`+TIUZ7wU8_V?DcaQFx*d zq^-W?-ic9p(4p og%CmrA%qY@2qA>HCw+bY0njb%3a|(BWB>pF07*qoM6N<$f+kHdM*si- literal 0 HcmV?d00001 diff --git a/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/off.png b/extensions/pyRevitDevTools.extension/pyRevitDev.tab/Debug.panel/Bundle Tests.pulldown/Test Reload Icon Toggle.smartbutton/off.png new file mode 100644 index 0000000000000000000000000000000000000000..5da357996a3103efe03c53619c320bf99d2728de GIT binary patch literal 1818 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 Date: Mon, 6 Jul 2026 21:20:54 -0400 Subject: [PATCH 2/2] keep log severity --- .../pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py b/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py index b46b4e8b48..56b06ca043 100644 --- a/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py +++ b/extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Update.smartbutton/script.py @@ -24,7 +24,7 @@ def __selfinit__(script_cmp, ui_button_cmp, __rvt__): ui_button_cmp.set_icon(has_update_icon, icon_size=ribbon.ICON_LARGE) except Exception as ex: - logger.debug('Error in checking updates: %s', ex) + logger.error('Error in checking updates: %s', ex) return True