feat: Allow for an option to close other outputs for all scripts in pyrevit settings#2793
Conversation
… ScriptConsole.cs
…tiple revit instances
There was a problem hiding this comment.
PR Summary:
This PR adds a new configuration option to automatically close Script Consoles across multiple script runs. Users can enable "Minimize the number of open consoles" and choose between closing current command consoles or all consoles. The implementation is done at the C# ScriptConsole level to catch all output types, with corresponding UI changes in the Settings window and clean engine run for consistency.
Review Summary:
The PR implements a valuable feature with a solid architectural approach. The C# implementation ensures all output types are handled, not just those using Python's script.get_output(). However, there are several critical code quality issues that need to be addressed, particularly duplicate imports causing maintenance problems, empty catch blocks violating repository standards, and fragile configuration parsing logic that could cause incorrect behavior.
Follow-up suggestions:
| } | ||
| return (close, mode); | ||
| } | ||
| catch { |
There was a problem hiding this comment.
Empty catch block without logging. This makes debugging difficult if config reading fails. Consider adding logging or at least a comment explaining why exceptions are silently ignored.
catch (Exception ex) {
// Log configuration parsing error for troubleshooting
System.Diagnostics.Debug.WriteLine($"Failed to read pyRevit config: {ex.Message}");
return (false, "current_command");
}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.
| var filterId = filterByCommandId ? this.OutputId : null; | ||
| ScriptConsoleManager.CloseActiveOutputWindows(excludeOutputWindow: this, filterOutputWindowId: filterId); | ||
| } | ||
| catch { |
There was a problem hiding this comment.
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.
| if (!inCore) continue; | ||
|
|
||
| if (line.StartsWith("close_other_outputs", StringComparison.InvariantCultureIgnoreCase)) | ||
| close = line.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) >= 0; |
There was a problem hiding this comment.
Config parsing logic is fragile and could match unintended lines. Using IndexOf to find "true" anywhere in the line could match comments or other values. Should parse the key=value format properly.
if (line.StartsWith("close_other_outputs", StringComparison.InvariantCultureIgnoreCase)) {
var parts = line.Split(new[] { '=' }, 2);
if (parts.Length == 2)
close = string.Equals(parts[1].Trim().Trim('"'), "true", StringComparison.InvariantCultureIgnoreCase);
}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.
|
@MohamedAsli |
There was a problem hiding this comment.
Pull Request Overview
This PR introduces a new configuration option to automatically close Script Consoles across multiple script runs. The feature allows users to minimize console clutter by automatically closing existing output windows when new scripts are executed.
- Adds configuration properties
output_close_othersandoutput_close_modeto userconfig - Implements UI controls in the Settings window with localization support
- Integrates console closing logic into the C# ScriptConsole class
Reviewed Changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pyrevitlib/pyrevit/userconfig.py | Adds duplicate imports and new configuration properties for console closing behavior |
| extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Settings.smartbutton/script.py | Implements UI binding logic for the new console closing options |
| extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Settings.smartbutton/bundle.yaml | Adds clean engine configuration to prevent cached option displays |
| extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Settings.smartbutton/SettingsWindow.xaml | Adds UI controls for console closing options with data binding |
| extensions/pyRevitCore.extension/pyRevit.tab/pyRevit.panel/Settings.smartbutton/SettingsWindow.ResourceDictionary.*.xaml | Provides localization strings for the new console closing feature |
| dev/pyRevitLabs.PyRevit.Runtime/ScriptConsole.cs | Implements configuration reading and console closing logic in C# |
| if (line.StartsWith("close_other_outputs", StringComparison.InvariantCultureIgnoreCase)) | ||
| close = line.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) >= 0; |
There was a problem hiding this comment.
The configuration value parsing is unreliable. Using IndexOf to check for 'true' could match partial strings like 'untrue' or comments containing 'true'. Should parse the actual value after the '=' separator like the close_mode parsing below.
| if (line.StartsWith("close_other_outputs", StringComparison.InvariantCultureIgnoreCase)) | |
| close = line.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) >= 0; | |
| if (line.StartsWith("close_other_outputs", StringComparison.InvariantCultureIgnoreCase)) { | |
| var parts = line.Split(new[] { '=' }, 2); | |
| if (parts.Length == 2) | |
| close = parts[1].Trim().Trim('"').Equals("true", StringComparison.InvariantCultureIgnoreCase); | |
| } |
.../pyRevit.tab/pyRevit.panel/Settings.smartbutton/SettingsWindow.ResourceDictionary.fr_fr.xaml
Outdated
Show resolved
Hide resolved
Update SettingsWindow.ResourceDictionary.fr_fr.xaml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
dosymep
left a comment
There was a problem hiding this comment.
general recommendations:
- add the implementation of getting and setting values in the PyRevitConfigs.cs class
- add constants of names and possible values to the PyRevitConsts.cs class,
- add the implementation of getting and setting values to the userconfig.py class
- optional add support for the new configuration to the pyrevit.exe console application
| return ScriptConsoleConfigs.DOCTYPE + head.OuterHtml + ActiveDocument.Body.OuterHtml; | ||
| } | ||
|
|
||
| private static (bool closeOthers, string closeMode) ReadCloseOtherOutputsSetting() { |
There was a problem hiding this comment.
I recommend using the configuration API, which is already in the pyRevit library. It is also worth adding these settings to the command line utility :)
| this.Dispatcher.BeginInvoke(new Action(() => { | ||
| if (string.Equals(mode, "current_command", StringComparison.InvariantCultureIgnoreCase)) | ||
| CloseOtherOutputs(filterByCommandId: true); | ||
| else if (string.Equals(mode, "close_all", StringComparison.InvariantCultureIgnoreCase)) { | ||
| CloseOtherOutputs(filterByCommandId: false); | ||
| } | ||
| })); |
There was a problem hiding this comment.
It is better to put the operating modes in an enum or create a class hierarchy
There was a problem hiding this comment.
Thanks a lot!
Here, would you add extension methods to map enum and string from Consts file ?
I'm a beginner in C# so plz don't be implicit :)
There was a problem hiding this comment.
after writing the review, I think it will be enough to create text constants with which to conduct comparisons
but you can still make an enum Enum.TryParse<TEnum>(string value, out TEnum result)
|
|
||
| self.minimize_consoles_cb.IsChecked = user_config.output_close_others | ||
|
|
||
| if user_config.output_close_mode == 'current_command': |
There was a problem hiding this comment.
It is better to put the operating modes in an enum or create a class hierarchy
pyrevitlib/pyrevit/userconfig.py
Outdated
| def output_close_others(self): | ||
| """Whether to close other output windows.""" | ||
| return self.core.get_option( | ||
| 'close_other_outputs', |
There was a problem hiding this comment.
It is recommended to add configuration constants to the constants class
There was a problem hiding this comment.
I think it's also worth moving these settings to this class
There was a problem hiding this comment.
I think it's also worth moving these settings to this class
Didn't understand
They already are in the PyRevitConfig class
There was a problem hiding this comment.
write an implementation of access to these parameters inside a class specially made for access to the pyRevit_Config.ini parameters
sample:
@property
def close_other_outputs(self):
return self.core.get_option(
CONSTS.CloseOtherOutputs,
default_value=CONSTS. CloseOtherOutputsDefault,
)
@close_other_outputs.setter
def close_other_outputs(self, state):
self.core.set_option(
CONSTS.CloseOtherOutputs,
value=stateThere was a problem hiding this comment.
Sorry, I didn't notice that you wrote the code where it should be :D
pyrevitlib/pyrevit/userconfig.py
Outdated
| @output_close_others.setter | ||
| def output_close_others(self, state): | ||
| self.core.set_option( | ||
| 'close_other_outputs', |
There was a problem hiding this comment.
It is recommended to add configuration constants to the constants class
There was a problem hiding this comment.
I think it's also worth moving these settings to this class
pyrevitlib/pyrevit/userconfig.py
Outdated
| def output_close_mode(self): | ||
| """Output window closing mode: 'current_command' or 'close_all'.""" | ||
| return self.core.get_option( | ||
| 'close_mode', |
There was a problem hiding this comment.
It is recommended to add configuration constants to the constants class
There was a problem hiding this comment.
I think it's also worth moving these settings to this class
pyrevitlib/pyrevit/userconfig.py
Outdated
| @output_close_mode.setter | ||
| def output_close_mode(self, mode): | ||
| self.core.set_option( | ||
| 'close_mode', |
There was a problem hiding this comment.
It is recommended to add configuration constants to the constants class
There was a problem hiding this comment.
I think it's also worth moving these settings to this class
| var cfg = GetConfigFile(); | ||
| var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey); | ||
|
|
||
| var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\''); |
There was a problem hiding this comment.
@dosymep is there a better way than trimming the config value ?
There was a problem hiding this comment.
As far as I remember, you don't need to trim it yourself, it should do it automatically
| def output_close_mode_enum(self): | ||
| """Output window closing mode as enum (CurrentCommand | CloseAll).""" | ||
| value = self.core.get_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| default_value=CONSTS.ConfigsCloseOutputModeDefault, | ||
| ) | ||
| if not value: | ||
| value = CONSTS.ConfigsCloseOutputModeDefault | ||
|
|
||
| @output_close_mode.setter | ||
| def output_close_mode(self, mode): | ||
| self.core.set_option( | ||
| 'close_mode', | ||
| value=mode | ||
| ) | ||
| value_lc = str(value).lower() | ||
|
|
||
| if value_lc == str(CONSTS.ConfigsCloseOutputModeCloseAll).lower(): | ||
| return PyRevit.OutputCloseMode.CloseAll | ||
| else: | ||
| return PyRevit.OutputCloseMode.CurrentCommand | ||
|
|
||
| @output_close_mode_enum.setter | ||
| def output_close_mode_enum(self, mode): | ||
| """Store string in INI, mapped from enum.""" | ||
| if mode == PyRevit.OutputCloseMode.CloseAll: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCloseAll | ||
| ) | ||
| else: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCurrentCommand | ||
| ) |
There was a problem hiding this comment.
@dosymep With enum, I had to deal with mapping somewhere.
Feel free to redirect me, if you see a better way to do plz
There was a problem hiding this comment.
I think it's good, I hope in the future I will rewrite all the code for working with settings, and there will be a more convenient option
There was a problem hiding this comment.
remove only the enum suffix in name, I don't think users need to see it
|
@devloai review again taking into account all the comments from Dosymep and MohamedAsli and latest changes |
There was a problem hiding this comment.
PR Summary:
This PR introduces a new configuration option to automatically close Script Console windows when new scripts run. Users can enable "Minimize the number of open consoles" and choose between closing current command consoles or all consoles. The implementation spans C# configuration management, UI settings integration, and multi-language localization.
Review Summary:
Reviewed the cross-language implementation covering C# configuration layer, Python user config integration, and XAML UI components. Applied pyRevit's exception handling standards from the validation checklist and evaluated performance implications of UI marshalling patterns. The feature architecture follows established pyRevit configuration patterns well.
Follow-up suggestions:
| var filterId = filterByCommandId ? this.OutputId : null; | ||
| ScriptConsoleManager.CloseActiveOutputWindows(excludeOutputWindow: this, filterOutputWindowId: filterId); | ||
| } | ||
| catch { |
There was a problem hiding this comment.
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.
| if (PyRevitConfigs.GetCloseOtherOutputs()) | ||
| { | ||
| var mode = PyRevitConfigs.GetCloseOutputMode(); | ||
| this.Dispatcher.BeginInvoke(new Action(() => |
There was a problem hiding this comment.
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.
| var cfg = GetConfigFile(); | ||
| var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey); | ||
|
|
||
| var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\''); |
There was a problem hiding this comment.
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.
|
@dosymep your expertise is still needed here 😊 |
| var cfg = GetConfigFile(); | ||
| var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey); | ||
|
|
||
| var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\''); |
There was a problem hiding this comment.
As far as I remember, you don't need to trim it yourself, it should do it automatically
pyrevitlib/pyrevit/userconfig.py
Outdated
| def output_close_others(self): | ||
| """Whether to close other output windows.""" | ||
| return self.core.get_option( | ||
| 'close_other_outputs', |
There was a problem hiding this comment.
Sorry, I didn't notice that you wrote the code where it should be :D
| def output_close_mode_enum(self): | ||
| """Output window closing mode as enum (CurrentCommand | CloseAll).""" | ||
| value = self.core.get_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| default_value=CONSTS.ConfigsCloseOutputModeDefault, | ||
| ) | ||
| if not value: | ||
| value = CONSTS.ConfigsCloseOutputModeDefault | ||
|
|
||
| @output_close_mode.setter | ||
| def output_close_mode(self, mode): | ||
| self.core.set_option( | ||
| 'close_mode', | ||
| value=mode | ||
| ) | ||
| value_lc = str(value).lower() | ||
|
|
||
| if value_lc == str(CONSTS.ConfigsCloseOutputModeCloseAll).lower(): | ||
| return PyRevit.OutputCloseMode.CloseAll | ||
| else: | ||
| return PyRevit.OutputCloseMode.CurrentCommand | ||
|
|
||
| @output_close_mode_enum.setter | ||
| def output_close_mode_enum(self, mode): | ||
| """Store string in INI, mapped from enum.""" | ||
| if mode == PyRevit.OutputCloseMode.CloseAll: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCloseAll | ||
| ) | ||
| else: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCurrentCommand | ||
| ) |
There was a problem hiding this comment.
I think it's good, I hope in the future I will rewrite all the code for working with settings, and there will be a more convenient option
| def output_close_mode_enum(self): | ||
| """Output window closing mode as enum (CurrentCommand | CloseAll).""" | ||
| value = self.core.get_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| default_value=CONSTS.ConfigsCloseOutputModeDefault, | ||
| ) | ||
| if not value: | ||
| value = CONSTS.ConfigsCloseOutputModeDefault | ||
|
|
||
| @output_close_mode.setter | ||
| def output_close_mode(self, mode): | ||
| self.core.set_option( | ||
| 'close_mode', | ||
| value=mode | ||
| ) | ||
| value_lc = str(value).lower() | ||
|
|
||
| if value_lc == str(CONSTS.ConfigsCloseOutputModeCloseAll).lower(): | ||
| return PyRevit.OutputCloseMode.CloseAll | ||
| else: | ||
| return PyRevit.OutputCloseMode.CurrentCommand | ||
|
|
||
| @output_close_mode_enum.setter | ||
| def output_close_mode_enum(self, mode): | ||
| """Store string in INI, mapped from enum.""" | ||
| if mode == PyRevit.OutputCloseMode.CloseAll: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCloseAll | ||
| ) | ||
| else: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCurrentCommand | ||
| ) |
There was a problem hiding this comment.
remove only the enum suffix in name, I don't think users need to see it
|
@dosymep do you mind taking a last look at it and let us know if it is good enough to be tested and merged? |
dosymep
left a comment
There was a problem hiding this comment.
I suggest several possible solutions.
- Use an enum for the operating modes (but my solution needs to be double-checked).
- Write the entire code without using an enum, only for string comparisons (if the first option doesn't work for some reason).
| var cfg = GetConfigFile(); | ||
| var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey); | ||
|
|
||
| var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\''); |
There was a problem hiding this comment.
remove trim, the pyRevit library trimmed values by default
There was a problem hiding this comment.
public static OutputCloseMode GetCloseOutputMode()
{
var cfg = GetConfigFile();
var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey);
return Enum.TryParse<OutputCloseMode>(raw, true, out OutputCloseMode outCloseMode)
? outCloseMode
: 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); | ||
| } |
There was a problem hiding this comment.
public static void SetCloseOutputMode(OutputCloseMode outputCloseMode)
{
var cfg = GetConfigFile();
cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey, outputCloseMode.ToString());
}| public const string ConfigsCloseOutputModeDefault = "currentcommand"; | ||
| public const string ConfigsCloseOutputModeCurrentCommand = "currentcommand"; | ||
| public const string ConfigsCloseOutputModeCloseAll = "closeall"; |
| @output_close_mode_enum.setter | ||
| def output_close_mode_enum(self, mode): | ||
| """Store string in INI, mapped from enum.""" | ||
| if mode == PyRevit.OutputCloseMode.CloseAll: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCloseAll | ||
| ) | ||
| else: | ||
| self.core.set_option( | ||
| CONSTS.ConfigsCloseOutputModeKey, | ||
| value=CONSTS.ConfigsCloseOutputModeCurrentCommand |
There was a problem hiding this comment.
@property
def output_close_mode(self):
"""Output window closing mode as enum (CurrentCommand | CloseAll)."""
value = self.core.get_option(
CONSTS.ConfigsCloseOutputModeKey,
default_value=CONSTS.ConfigsCloseOutputModeDefault,
)
if System.Enum.TryParse[PyRevit.OutputCloseMode](value, true, out PyRevit.OutputCloseMode output_close_mode):
return output_close_mode
return PyRevit.OutputCloseMode.CurrentCommand
@output_close_mode.setter
def output_close_mode(self, output_close_mode):
"""Store string in INI, mapped from enum."""
self.core.set_option(
CONSTS.ConfigsCloseOutputModeKey,
value=str(output_close_mode)
)There was a problem hiding this comment.
I renamed methods (remove enum suffix)
|
@jmcouffin, I’ll test this as soon as I have a moment to breathe |
|
I did test it, It works fine |
jmcouffin
left a comment
There was a problem hiding this comment.
Tested again this morning. It works as expected and the thread is already a pain to load in github. I will approve and merge and ask you guys to make the improvements in a future PR
|
Some AI review that my be useful for future changes: Code Review and fixes
Here are the before/after code sections based on the review feedback: 1. GetCloseOutputMode() - Remove unnecessary trimmingBefore: public static OutputCloseMode GetCloseOutputMode()
{
var cfg = GetConfigFile();
var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey);
var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\'');
if (string.Equals(s, PyRevitConsts.ConfigsCloseOutputModeCloseAll, StringComparison.InvariantCultureIgnoreCase))
return OutputCloseMode.CloseAll;
else
return OutputCloseMode.CurrentCommand;
}After: public static OutputCloseMode GetCloseOutputMode()
{
var cfg = GetConfigFile();
var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey);
return Enum.TryParse<OutputCloseMode>(raw, true, out OutputCloseMode outCloseMode)
? outCloseMode
: OutputCloseMode.CurrentCommand;
}2. SetCloseOutputMode() - Simplify enum conversionBefore: 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);
}After: public static void SetCloseOutputMode(OutputCloseMode outputCloseMode)
{
var cfg = GetConfigFile();
cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey, outputCloseMode.ToString());
}3. PyRevitConsts.cs - Remove unnecessary constantsBefore: public const string ConfigsCloseOutputModeDefault = "currentcommand";
public const string ConfigsCloseOutputModeCurrentCommand = "currentcommand";
public const string ConfigsCloseOutputModeCloseAll = "closeall";After: // Remove these constants - enum ToString() handles it4. Python userconfig.py - Simplify enum handlingBefore: @property
def output_close_mode_enum(self):
"""Output window closing mode as enum (CurrentCommand | CloseAll)."""
value = self.core.get_option(
CONSTS.ConfigsCloseOutputModeKey,
default_value=CONSTS.ConfigsCloseOutputModeDefault,
)
if not value:
value = CONSTS.ConfigsCloseOutputModeDefault
value_lc = str(value).lower()
if value_lc == str(CONSTS.ConfigsCloseOutputModeCloseAll).lower():
return PyRevit.OutputCloseMode.CloseAll
else:
return PyRevit.OutputCloseMode.CurrentCommand
@output_close_mode_enum.setter
def output_close_mode_enum(self, mode):
"""Store string in INI, mapped from enum."""
if mode == PyRevit.OutputCloseMode.CloseAll:
self.core.set_option(
CONSTS.ConfigsCloseOutputModeKey,
value=CONSTS.ConfigsCloseOutputModeCloseAll
)
else:
self.core.set_option(
CONSTS.ConfigsCloseOutputModeKey,
value=CONSTS.ConfigsCloseOutputModeCurrentCommand
)After: @property
def output_close_mode(self):
"""Output window closing mode as enum (CurrentCommand | CloseAll)."""
value = self.core.get_option(
CONSTS.ConfigsCloseOutputModeKey,
default_value=CONSTS.ConfigsCloseOutputModeDefault,
)
if System.Enum.TryParse[PyRevit.OutputCloseMode](value, True, out PyRevit.OutputCloseMode output_close_mode):
return output_close_mode
return PyRevit.OutputCloseMode.CurrentCommand
@output_close_mode.setter
def output_close_mode(self, output_close_mode):
"""Store string in INI, mapped from enum."""
self.core.set_option(
CONSTS.ConfigsCloseOutputModeKey,
value=str(output_close_mode)
)5. Empty catch blocks - Add loggingBefore: try
{
var filterId = filterByCommandId ? this.OutputId : null;
ScriptConsoleManager.CloseActiveOutputWindows(excludeOutputWindow: this, filterOutputWindowId: filterId);
}
catch { }After: try
{
var filterId = filterByCommandId ? this.OutputId : null;
ScriptConsoleManager.CloseActiveOutputWindows(excludeOutputWindow: this, filterOutputWindowId: filterId);
}
catch (Exception ex)
{
logger.Debug("Failed to close other output windows: " + ex.Message);
} |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25296+0837-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25296+0923-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25300+1037-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25301+1412-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25301+1521-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25302+0921-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25302+0949-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25302+1503-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25307+1644-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25307+1935-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25307+2011-wip |
|
📦 New work-in-progress (wip) builds are available for 5.2.0.25307+2045-wip |
|
📦 New work-in-progress (wip) builds are available for 5.3.0.25307+2237-wip |
|
📦 New work-in-progress (wip) builds are available for 5.3.0.25308+0721-wip |
|
📦 New public release are available for 5.3.0.25307+2146 |

Description
This PR introduces a new configuration option to automatically close Script Consoles across multiple script runs.
Users can now enable Minimize the number of open consoles and choose between:
I initially tried to implement this at the Python level (
script.get_output()), but that only worked for scripts explicitly using that variable, meaning print_md, print_html, print_table, etc. were not caught. To ensure all outputs are handled, I implemented the logic in the C# ScriptConsole file (Would appreciate review on this part as I’m not a C# guy)@jmcouffin It's the first time I push C# code, I didn't include the compiled dlls, is that right ?
I've also added a clean engine to the Settings bundle file in order to avoid inconsistent option displays across Revit instances, since configs are cached in memory in the rocket mode.
Checklist
Before submitting your pull request, ensure the following requirements are met:
Related Issues
It was initially requested by @gtalarico in the #211, but @eirannejad only implemented it in the backend side and not it in the Settings window