pyRevit icon indicating copy to clipboard operation
pyRevit copied to clipboard

[Bug]: pyRevit tab Settings throws error after using custom tab

Open mysh38 opened this issue 5 months ago • 4 comments

✈ Pre-Flight checks

  • [x] I don't have SentinelOne antivirus installed (see above for the solution)
  • [x] I have searched in the issues (open and closed) but couldn't find a similar issue
  • [x] I have searched in the pyRevit Forum for similar issues
  • [x] I already followed the installation troubleshooting guide thoroughly
  • [x] I am using the latest pyRevit Version

🐞 Describe the bug

In our team we have a custom tab with pushbuttons. After I activate one of my buttons it executes as intended, but breaks any attempts to use some original buttons (Settings and Reload as i discovered) from pyRevit tab showing error messages until I restart Revit Everything works fine backwards - I can use pyRevit buttons then use my buttons after it. But anyway it will break the future attempts of executing pyRevit tab buttons

⌨ Error/Debug Message

IronPython Traceback:
Traceback (most recent call last):
 File "C:\pyRevit\master\extensions\pyRevitCore.extension\pyRevit.tab\pyRevit.panel\Settings.smartbutton\script.py", line 1013, in <module>
 File "C:\pyRevit\master\extensions\pyRevitCore.extension\pyRevit.tab\pyRevit.panel\Settings.smartbutton\script.py", line 107, in __init__
 File "C:\pyRevit\master\extensions\pyRevitCore.extension\pyRevit.tab\pyRevit.panel\Settings.smartbutton\script.py", line 227, in _setup_env_vars_list
TypeError: unsupported operand type(s) for <: 'tuple' and 'str'

Script Executor Traceback:
IronPython.Runtime.Exceptions.TypeErrorException: unsupported operand type(s) for <: 'tuple' and 'str'
в IronPython.Runtime.Operations.PythonOps.TypeErrorForBinaryOp(String opSymbol, Object x, Object y)
в IronPython.Runtime.Operations.PythonOps.RichCompare(CodeContext context, Object x, Object y, PythonOperationKind op)
в IronPython.Runtime.Operations.PythonOps.RichCompareSequences(CodeContext context, ReadOnlySpan`1 data0, ReadOnlySpan`1 data1, PythonOperationKind op)
в IronPython.Runtime.PythonTuple.op_LessThan(PythonTuple self, PythonTuple other)
в CallSite.Target(Closure , CallSite , Object , Object )
в IronPython.Runtime.PythonContext.DefaultPythonLtComparer.Compare(Object x, Object y)
в IronPython.Runtime.PythonList.DoCompare(Object[] keys, IComparer cmp, Int32 p, Int32 q, Boolean reverse)
в IronPython.Runtime.PythonList.ListMergeSort(Object[] sortData, Object[] keys, IComparer cmp, Int32 index, Int32 count, Boolean reverse)
в IronPython.Runtime.PythonList.DoSort(CodeContext context, IComparer cmp, Object key, Boolean reverse, Int32 index, Int32 count)
в IronPython.Runtime.PythonList.Sort(CodeContext context, Object key, Boolean reverse)
в IronPython.Runtime.PythonList.sort(CodeContext context, IDictionary`2 kwArgs)
в IronPython.Modules.Builtin.sorted(CodeContext context, Object iterable, IDictionary`2 kwArgs)
в Microsoft.Scripting.Interpreter.FuncCallInstruction`4.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.LightLambda.Run4[T0,T1,T2,T3,TRet](T0 arg0, T1 arg1, T2 arg2, T3 arg3)
в System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2)
в Microsoft.Scripting.Interpreter.DynamicInstruction`4.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
в IronPython.Runtime.Method.MethodBinding.SelfTarget(CallSite site, CodeContext context, Object target)
в System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
в Microsoft.Scripting.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.LightLambda.Run3[T0,T1,T2,TRet](T0 arg0, T1 arg1, T2 arg2)
в System.Dynamic.UpdateDelegates.UpdateAndExecute4[T0,T1,T2,T3,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
в IronPython.Runtime.Types.LateBoundInitBinder.FastInitSite`1.CallTarget(CallSite site, CodeContext context, Object inst, T0 arg0)
в System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2)
в IronPython.Runtime.Types.PythonType.FastTypeSite`1.CallTarget(CallSite site, CodeContext context, Object type, T0 arg0)
в System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2)
в Microsoft.Scripting.Interpreter.DynamicInstruction`4.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
в Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
в IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx)
в PyRevitLabs.PyRevit.Runtime.IronPythonEngine.Execute(ScriptRuntime& runtime)

♻️ To Reproduce

  1. Start Revit
  2. Load the model
  3. Activate any custom button
  4. Activate original pyRevit button (Settings, Reload)
  5. Get error

⏲️ Expected behavior

Working pyRevit buttons

🖥️ Hardware and Software Setup (please complete the following information)

==> Registered Clones (full git repos)
master | Deploy: "basepublic" | Branch: "master" | Version: "5.2.0.25181+1425" | Path: "C:\pyRevit\master"
==> Registered Clones (deployed from archive/image)
==> Attachments
master | Product: "2024.1" | Engine: IPY342 (342) | Path: "C:\pyRevit\master" | AllUsers
==> Installed Extensions
pyDEV | Type: Unknown | Repo: "" | Installed: "A:\IPPRI\General - Документы\PRJ_General\CAD\Revit\pyRevit\DEV\pyrevit-ippri-extension\pyDEV.extension"
pyIPPRI | Type: Unknown | Repo: "" | Installed: "\\SHARE\pyRevit$\IPPRI-Extensions\pyIPPRI.extension"
==> Default Extension Search Path
C:\Users\ShaidulinMR\AppData\Roaming\pyRevit\Extensions
==> Extension Search Paths
\\SHARE\pyRevit$\IPPRI-Extensions
A:\IPPRI\General - Документы\PRJ_General\CAD\Revit\pyRevit\DEV\pyrevit-ippri-extension
==> Extension Sources - Default
https://github.com/pyrevitlabs/pyRevit/raw/master/extensions/extensions.json
==> Extension Sources - Additional
==> Installed Revits
2026 First Customer Ship | Version: 26.0.4.409 | Build: 20250227_1515(x64) | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2026\"
2024.1 | Version: 24.1.0.66 | Build: 20230701_1515(x64) | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2024\"
==> Running Revit Instances
PID: 5268 | 2024.1 | Version: 24.1.0.66 | Build: 20230701_1515(x64) | Language: 0 | Path: "C:\Program Files\Autodesk\Revit 2024"
==> User Environment
Microsoft Windows 10 [Version 10.0.26100]
Executing User: CORP\ShaidulinMR
Active User: CORP\ShaidulinMR
Admin Access: No
%APPDATA%: "C:\Users\ShaidulinMR\AppData\Roaming"
Latest Installed .Net Framework: 8.0.15
No .Net Target Packs are installed.
No .Net-Core Target Packs are installed.
pyRevit CLI v5.2.0.25181+1332.ad837f1fc6ac89979e64710991eb6d846b63c109

Additional context

I've tried to make some research:

  • Some of my scripts contain #! python3 as first line, some not. Actually this line does not affect at all while I'm executing buttons from my custom tab (I made two dummy buttons with print output, one of them contains #! python3, both run normally but break some pyRevit buttons).
  • Buttons on pyRevit.tab I've found failing: Settings, Reload
  • Reload button returns Revit popup error window stating "Revit encountered an exception PythonEngine is not initialized"
  • Tested and working pyRevit buttons: Tab Coloring, About, emojis, apidocs, Isolate, Print Sheets, Who Did That

Way of installation

I'm performing deployment via two ps1 scripts:

1_installCLI.ps1

New-Item -ItemType Directory -Path "$env:USERPROFILE\AppData\Roaming\pyRevit" -Force
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }

Write-Output "Installing .Net (4.8.1)..."
Start-Process -FilePath "\\ZEUS\Install\NDP481-x86-x64-AllOS-ENU.exe" `
              -ArgumentList "/q /norestart" `
              -Wait `
              -Verb RunAs > $null

Write-Output "Installing pyRevitCLI..."
Start-Process -FilePath "msiexec.exe" `
              -Wait `
              -ArgumentList "/i `"\\ZEUS\Install\pyRevit_CLI_5.2.0.25181_admin_signed.msi`" /qn /norestart" > $null

Write-Output "Configuring PATH..."
$target = [EnvironmentVariableTarget]::Machine
$pyrevitPath = "C:\Program Files\pyRevit CLI"
$currentPath = [Environment]::GetEnvironmentVariable("Path", $target)
$pathParts = $currentPath -split ';'
if (-not ($pathParts -contains $pyrevitPath)) {
    $newPath = "$currentPath;$pyrevitPath"
    [Environment]::SetEnvironmentVariable("Path", $newPath, $target)
}

2_clone_pyrevit.ps1

New-Item -ItemType Directory -Path "$env:USERPROFILE\AppData\Roaming\pyRevit" -Force

function Confirm-Path ([string] $targetpath) {
    Write-Output "Confirming $($targetpath)"
    If (Test-Path $targetpath) {
        Remove-Item -Path $targetpath -Recurse -Force
    }
    New-Item -ItemType Directory -Force -Path $targetpath > $null
}

function Clone-PyRevit() {
    Confirm-Path $pyrevitroot
    Confirm-Path $pyrevitexts

    pyrevit revits killall
    pyrevit clones forget $pyrevitclonename > $null

    # Attempt to clone with up to 5 retries
    $maxAttempts = 5
    $attempt = 0
    $success = $false

    Write-Output "Cloning Core..."
    while (-not $success -and $attempt -lt $maxAttempts) {
        $attempt++
        Write-Output "Attempt $attempt of $maxAttempts"
        try {
            # clone pyRevit now
            pyrevit clone $ourclonename --dest=$pyrevitcoredest --debug
            $success = $true
            Write-Output "Clone successful on attempt $attempt."
        } catch {
            Write-Output "Attempt $attempt failed: $_"
            Start-Sleep -Seconds 5  # wait briefly before retrying
        }
    }
    if (-not $success) {
        Write-Error "Failed to clone pyRevit after $maxAttempts attempts."
        exit 1
    }
}

function Configure-PyRevit() {
    Write-Output "Configuring pyRevit..."
    pyrevit config --from="\\Share\pyrevit$\pyRevit_config.ini"
}

function Attach-PyRevit() {
    pyrevit clones add $ourclonename $(Join-Path $pyrevitcoredest $ourclonename)
    pyrevit attach $ourclonename 342 2024 --allusers
}

function Test-CommandExists {
    param ($command)
    $oldPreference = $ErrorActionPreference
    $ErrorActionPreference = 'stop'
    try {
        if(Get-Command $command){
            return $true
        }
    }
    catch {
        Write-Output "cannot execute pyRevitCLI"
        return $false
    }
    finally {
        $ErrorActionPreference=$oldPreference
    }
}


$pyrevitroot = "C:\pyRevit"
$ourclonename = "master"
$pyrevitcoredest = $pyrevitroot
$pyrevitexts = $(Join-Path $pyrevitroot "Extensions")

# orchestrate
if (Test-CommandExists "pyrevit") {
    Clone-PyRevit
    Configure-PyRevit
    Attach-PyRevit
}

Extra info:

  • External revit add-ins: Autodesk Vault
  • Antivirus: Windows Defender
  • Installed python in system: 3.9.12, 3.12.3

This report might look a bit messy so I'm ready to provide more details/logs/etc about this error

mysh38 avatar Jul 08 '25 10:07 mysh38

@devloai have a look and identify issue

jmcouffin avatar Jul 08 '25 11:07 jmcouffin

I'll analyze the Settings button code to identify the root cause of the sorting error with mixed data types in environment variables and propose a fix.

Status: Done.

devloai[bot] avatar Jul 08 '25 11:07 devloai[bot]

Analysis and Solution Plan

I've identified the root cause of this issue! The problem occurs in the Settings button script at line 228 where the environment variables are being sorted.

Root Cause

The error TypeError: unsupported operand type(s) for <: 'tuple' and 'str' happens because:

  1. envvars.get_pyrevit_env_vars().items() returns tuples of (key, value) pairs
  2. When custom extensions set environment variables with different value types (tuples, lists, objects), IronPython's sorted() function fails
  3. The sorting algorithm tries to compare mixed types, which IronPython cannot handle

Solution Plan

1. Fix the immediate sorting issue

  • Modify _setup_env_vars_list() method to sort only by keys (strings) instead of the entire tuple
  • Add defensive programming to handle cases where get_pyrevit_env_vars() returns None
  • Ensure robust type handling for environment variable values

2. Implement defensive coding

  • Add try-except blocks around the sorting operation
  • Provide fallback behavior if sorting fails
  • Add logging for debugging purposes

3. Handle edge cases

  • Check if environment variables dictionary exists before processing
  • Handle cases where custom extensions might set unusual value types
  • Ensure the EnvVariable class can handle various value types properly

Implementation Details

The fix will involve changing line 228 from:

EnvVariable(k, v) for k, v in sorted(envvars.get_pyrevit_env_vars().items())

To a more robust approach that:

  • Sorts by keys only (which are always strings)
  • Handles None values gracefully
  • Provides error recovery

This fix will maintain backward compatibility while preventing the crash that occurs when custom extensions set environment variables with complex types.

Ready to implement this fix? Please confirm the approach and I'll proceed with the implementation.

devloai[bot] avatar Jul 08 '25 11:07 devloai[bot]

Adding a , key=lambda x: x[0] inside the sorted function should fix that, but I'd be curious to know why this happens...

sanzoghenzo avatar Aug 16 '25 20:08 sanzoghenzo