Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions dev/pyRevitLabs.PyRevit.Runtime/ScriptConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using pyRevitLabs.Common;
using pyRevitLabs.CommonWPF.Controls;
using pyRevitLabs.Emojis;
using pyRevitLabs.PyRevit;

namespace PyRevitLabs.PyRevit.Runtime {
public struct ScriptConsoleDebugger {
Expand Down Expand Up @@ -171,7 +172,7 @@ public partial class ScriptConsole : ScriptConsoleTemplate, IComponentConnector,
private System.Windows.Forms.HtmlElement _lastDocumentBody = null;
private UIApplication _uiApp;

private List<ScriptConsoleDebugger> _supportedDebuggers =
private List<ScriptConsoleDebugger> _supportedDebuggers =
new List<ScriptConsoleDebugger> {
new ScriptConsoleDebugger() {
Name = "Pdb (IronPython|CPython)",
Expand Down Expand Up @@ -376,6 +377,27 @@ public string GetFullHtml() {
return ScriptConsoleConfigs.DOCTYPE + head.OuterHtml + ActiveDocument.Body.OuterHtml;
}

private void ApplyCloseOthersConfig()
{
if (PyRevitConfigs.GetCloseOtherOutputs())
{
var mode = PyRevitConfigs.GetCloseOutputMode();
this.Dispatcher.BeginInvoke(new Action(() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using BeginInvoke for UI marshalling may introduce unnecessary delay. Consider whether the console closing operation needs to be asynchronous or if Invoke (synchronous) would be more appropriate for immediate feedback.

The current implementation may cause timing issues where the new console opens before others finish closing.

actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

{
CloseOtherOutputs(filterByCommandId: mode == OutputCloseMode.CurrentCommand);
}));
}
}

public void CloseOtherOutputs(bool filterByCommandId = true) {
try {
var filterId = filterByCommandId ? this.OutputId : null;
ScriptConsoleManager.CloseActiveOutputWindows(excludeOutputWindow: this, filterOutputWindowId: filterId);
}
catch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty catch block without logging. UI operations can fail for various reasons and silent failures make troubleshooting difficult.

catch (Exception ex) {
    // Log UI operation error for troubleshooting
    System.Diagnostics.Debug.WriteLine($"Failed to close other outputs: {ex.Message}");
}
actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty catch block violates exception handling standards. According to the review validation checklist, empty catch blocks should log exceptions at minimum. Consider logging the exception or using more specific exception types.

catch (Exception ex) {
    logger.Debug("Failed to close other output windows: " + ex.Message);
}
actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

}
}

private void SetupDefaultPage(string styleSheetFilePath = null) {
string cssFilePath;
if (styleSheetFilePath != null)
Expand Down Expand Up @@ -448,7 +470,7 @@ public void FocusOutput() {

public System.Windows.Forms.HtmlElement ComposeEntry(string contents, string HtmlElementType) {
WaitReadyBrowser();

// order is important
// "<" ---> &lt;
contents = ScriptConsoleConfigs.EscapeForHtml(contents);
Expand Down Expand Up @@ -552,7 +574,7 @@ public string GetInput() {
dbgMode = true;
}
}

// if no debugger, find other patterns
if (!dbgMode &&
new string[] { "select", "file" }.All(x => lastLine.Contains(x)))
Expand Down Expand Up @@ -782,6 +804,7 @@ public void SelfDestructTimer(int seconds) {
private void Window_Loaded(object sender, System.EventArgs e) {
var outputWindow = (ScriptConsole)sender;
ScriptConsoleManager.AppendToOutputWindowList(this);
ApplyCloseOthersConfig();
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
Expand Down
45 changes: 45 additions & 0 deletions dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfigs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public enum PyRevitLogLevels
Debug
}

public enum OutputCloseMode
{
CurrentCommand,
CloseAll
}

public static class PyRevitConfigs
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
Expand Down Expand Up @@ -505,6 +511,45 @@ public static void SetLoadBetaTools(bool state)
cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsLoadBetaKey, state);
}

// close other outputs config
public static bool GetCloseOtherOutputs()
{
var cfg = GetConfigFile();
var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOtherOutputsKey);
return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsCloseOtherOutputsDefault;
}

public static void SetCloseOtherOutputs(bool state)
{
var cfg = GetConfigFile();
cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOtherOutputsKey, state);
}

public static OutputCloseMode GetCloseOutputMode()
{
var cfg = GetConfigFile();
var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey);

var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\'');
Copy link
Contributor Author

@MohamedAsli MohamedAsli Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dosymep is there a better way than trimming the config value ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string trimming logic with both quotation marks seems unusual and may indicate a configuration parsing issue. The standard pyRevit configuration pattern doesn't typically require quote trimming. Consider whether this complexity is necessary or if it masks an underlying issue.

var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim();
actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.


if (s.Equals(PyRevitConsts.ConfigsCloseOutputModeCloseAll, StringComparison.InvariantCultureIgnoreCase))
{
return OutputCloseMode.CloseAll;
}

return OutputCloseMode.CurrentCommand;
}

public static void SetCloseOutputMode(OutputCloseMode mode)
{
var cfg = GetConfigFile();
var value = (mode == OutputCloseMode.CloseAll)
? PyRevitConsts.ConfigsCloseOutputModeCloseAll
: PyRevitConsts.ConfigsCloseOutputModeCurrentCommand;

cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey, value);
}

// cpythonengine
public static int GetCpythonEngineVersion()
{
Expand Down
10 changes: 8 additions & 2 deletions dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static class PyRevitConsts {
public const string ReleaseDirName = "release";
public const string SitePackagesDirName = "site-packages";
public const string PyRevitfileFilename = "PyRevitfile";

public const string NetFxFolder = "netfx";
public const string NetCoreFolder = "netcore";

Expand Down Expand Up @@ -85,6 +85,12 @@ public static class PyRevitConsts {
public const int ConfigsMinDriveSpaceDefault = 0;
public const string ConfigsLoadBetaKey = "loadbeta";
public const bool ConfigsLoadBetaDefault = false;
public const string ConfigsCloseOtherOutputsKey = "closeotheroutputs";
public const bool ConfigsCloseOtherOutputsDefault = false;
public const string ConfigsCloseOutputModeKey = "closeoutputmode";
public const string ConfigsCloseOutputModeDefault = "currentcommand";
public const string ConfigsCloseOutputModeCurrentCommand = "currentcommand";
public const string ConfigsCloseOutputModeCloseAll = "closeall";
public const string ConfigsCPythonEngineKey = "cpyengine";
public const int ConfigsCPythonEngineDefault = 0;
public const string ConfigsLocaleKey = "user_locale";
Expand Down Expand Up @@ -179,7 +185,7 @@ public static class PyRevitConsts {
public static string FindConfigFileInDirectory(string sourcePath) {
var configMatcher = new Regex(ConfigsFileRegexPattern, RegexOptions.IgnoreCase);
// capture exceptions that might occur getting the files under sourcePath
//
//
try {
if (CommonUtils.VerifyPath(sourcePath))
foreach (string subFile in Directory.GetFiles(sourcePath))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@
<system:String x:Key="CoreSettings.Development.Description">Misc options for pyRevit development</system:String>
<system:String x:Key="CoreSettings.Development.LoadBeta">Load Beta Tools (Scripts with __beta__ = True, Reload is required)</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles">Close other open consoles</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.Description">
If enabled, pyRevit will close other output consoles when a new script is run.
This helps to reduce the number of open output windows. Activate this option, unless you want to compare
multiple output data.
</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles.Current">Current command consoles</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.Current.Description">
Close all console windows associated with the current running command.
</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles.CloseAll">All consoles</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.CloseAll.Description">
Close all currently open console windows, even those that are not associated with the running command.
</system:String>

<system:String x:Key="CoreSettings.Caching">Caching</system:String>
<system:String x:Key="CoreSettings.Caching.Button">Reset Caching to default</system:String>
<system:Double x:Key="CoreSettings.Caching.Button.With">200</system:Double>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@
<system:String x:Key="CoreSettings.Development.Description">Options diverses pour le développement de pyRevit</system:String>
<system:String x:Key="CoreSettings.Development.LoadBeta">Charger les outils bêta (Scripts avec __beta__ = True, le rechargement est requis)</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles">Fermer les consoles ouvertes</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.Description">
Si activé, pyRevit fermera les autres consoles de sortie lors de l'exécution d'un nouveau script.
Cela permet de réduire le nombre de fenêtres de sortie ouvertes. Activez cette option, sauf si vous
voulez comparer les sorties de plusieurs scripts.
</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles.Current">Consoles de la commande actuelle</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.Current.Description">
Fermer toutes les fenêtres de console associées à la commande en cours d'exécution.
</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles.CloseAll">Toutes les consoles</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.CloseAll.Description">
Fermer toutes les fenêtres de console actuellement ouvertes, même celles qui ne sont pas associées à la commande en cours.
</system:String>

<system:String x:Key="CoreSettings.Caching">Mise en cache</system:String>
<system:String x:Key="CoreSettings.Caching.Button">Reset la mise en cache par défaut</system:String>
<system:Double x:Key="CoreSettings.Caching.Button.With">200</system:Double>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@
<system:String x:Key="CoreSettings.Development.Description">Разные настройки разработки pyRevit</system:String>
<system:String x:Key="CoreSettings.Development.LoadBeta">Загружать бета-инструменты (Скрипты с __beta__ = True, требуется перезапуск)</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles">Закрывать другие открытые консоли</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.Description">
Если включено, pyRevit будет закрывать другие консоли вывода при запуске нового скрипта.
Это помогает уменьшить количество открытых окон вывода. Активируйте эту опцию, если вам
не требуется сравнивать данные из нескольких окон вывода.
</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles.Current">Консоли текущей команды</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.Current.Description">
Закрыть все окна консоли, связанные с текущей выполняемой командой.
</system:String>

<system:String x:Key="CoreSettings.CloseOtherConsoles.CloseAll">Все консоли</system:String>
<system:String x:Key="CoreSettings.CloseOtherConsoles.CloseAll.Description">
Закрыть все открытые в данный момент окна консоли, даже те, которые не связаны с выполняемой командой.
</system:String>

<system:String x:Key="CoreSettings.Caching">Кеширование</system:String>
<system:String x:Key="CoreSettings.Caching.Button">Сбросить настройки кеширования</system:String>
<system:Double x:Key="CoreSettings.Caching.Button.With">220</system:Double>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,23 @@
<ToggleButton Style="{StaticResource AnimatedSwitch}" Height="24" x:Name="loadbetatools_cb" IsChecked="False"/>
<TextBlock Margin="30,4,0,0" Text="{DynamicResource CoreSettings.Development.LoadBeta}"/>
</WrapPanel>

<StackPanel Margin="10,5,10,10">
<TextBlock FontWeight="Medium" Margin="0,5,0,0" Text="{DynamicResource CoreSettings.CloseOtherConsoles}"/>
<TextBlock TextWrapping="WrapWithOverflow" Margin="0,5,0,0" Text="{DynamicResource CoreSettings.CloseOtherConsoles.Description}"/>
<WrapPanel Margin="0,15,0,0">
<ToggleButton Style="{StaticResource AnimatedSwitch}" Height="24" x:Name="minimize_consoles_cb" IsChecked="False"/>
<TextBlock Margin="30,4,0,0" Text="Minimize the number of open consoles:" />
</WrapPanel>
</StackPanel>

<StackPanel Margin="26,0,0,10" IsEnabled="{Binding ElementName=minimize_consoles_cb, Path=IsChecked}">
<RadioButton x:Name="closewindows_current_rb" GroupName="console_close_mode" Margin="0,5,0,5" IsChecked="False" Content="{DynamicResource CoreSettings.CloseOtherConsoles.Current}"/>
<TextBlock TextWrapping="WrapWithOverflow" Margin="20,0,0,5" Text="{DynamicResource CoreSettings.CloseOtherConsoles.Current.Description}"/>

<RadioButton x:Name="closewindows_close_all_rb" GroupName="console_close_mode" Margin="0,10,0,5" IsChecked="False" Content="{DynamicResource CoreSettings.CloseOtherConsoles.CloseAll}"/>
<TextBlock TextWrapping="WrapWithOverflow" Margin="20,0,0,0" Text="{DynamicResource CoreSettings.CloseOtherConsoles.CloseAll.Description}"/>
</StackPanel>
</StackPanel>
</GroupBox>
<GroupBox Header="{DynamicResource CoreSettings.Caching}" Margin="0,10,0,0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ tooltip:

Zeigt die Konfigurationsdatei im Explorer an.
context: zero-doc
engine:
clean: true
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ def _setup_core_options(self):

self.loadbetatools_cb.IsChecked = user_config.load_beta

self.minimize_consoles_cb.IsChecked = user_config.output_close_others

mode = user_config.output_close_mode_enum
if mode == PyRevit.OutputCloseMode.CurrentCommand:
self.closewindows_current_rb.IsChecked = True
self.closewindows_close_all_rb.IsChecked = False
else:
self.closewindows_current_rb.IsChecked = False
self.closewindows_close_all_rb.IsChecked = True

def _setup_engines(self):
"""Sets up the list of available engines."""
attachment = user_config.get_current_attachment()
Expand Down Expand Up @@ -846,6 +856,12 @@ def _save_core_options(self):

user_config.load_beta = self.loadbetatools_cb.IsChecked

user_config.output_close_others = self.minimize_consoles_cb.IsChecked
if self.closewindows_current_rb.IsChecked:
user_config.output_close_mode_enum = PyRevit.OutputCloseMode.CurrentCommand
else:
user_config.output_close_mode_enum = PyRevit.OutputCloseMode.CloseAll

def _save_engines(self):
# set active cpython engine
engine_cfg = self.cpythonEngines.SelectedItem
Expand Down
30 changes: 15 additions & 15 deletions pyrevitlib/pyrevit/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def get_data_file(file_id, file_ext, add_cmd_name=False):
script.get_data_file('mydata', 'data', add_cmd_name=True)
```
'/pyRevit_2018_Command Name_mydata.data'


Data files are not cleaned up at pyRevit startup.
Script should manage cleaning up these files.
Expand Down Expand Up @@ -547,7 +547,7 @@ def load_ui(ui_instance, ui_file='ui.xaml', handle_esc=True, set_owner=True):
"""Load xaml file into given window instance.

If window instance defines a method named `setup` it
will be called after loading
will be called after loading

Args:
ui_instance (forms.WPFWindow): ui form instance
Expand Down Expand Up @@ -679,29 +679,29 @@ def store_data(slot_name, data, this_project=True):
```python
from pyrevit import revit
from pyrevit import script


class CustomData(object):
def __init__(self, count, element_ids):
self._count = count
# serializes the Revit native objects
self._elmnt_ids = [revit.serialize(x) for x in element_ids]

@property
def count(self):
return self._count

@property
def element_ids(self):
# de-serializes the Revit native objects
return [x.deserialize() for x in self._elmnt_ids]


mydata = CustomData(
count=3,
element_ids=[<DB.ElementId>, <DB.ElementId>, <DB.ElementId>]
)

script.store_data("Selected Elements", mydata)
```

Expand Down Expand Up @@ -740,24 +740,24 @@ def load_data(slot_name, this_project=True):
```python
from pyrevit import revit
from pyrevit import script


class CustomData(object):
def __init__(self, count, element_ids):
self._count = count
# serializes the Revit native objects
self._elmnt_ids = [revit.serialize(x) for x in element_ids]

@property
def count(self):
return self._count

@property
def element_ids(self):
# de-serializes the Revit native objects
return [x.deserialize() for x in self._elmnt_ids]


mydata = script.load_data("Selected Elements")
mydata.element_ids
```
Expand Down
Loading