Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4a13527
Phase 1 - parse
ChrisCrosley May 16, 2026
c5ac910
Phase 2 - backwards compatibility, CLI tools, UI
ChrisCrosley May 16, 2026
13b8803
bug fixes. extension layouts for Core and Tools. UI fixes
ChrisCrosley May 16, 2026
4035609
optionally disable custom layouts
ChrisCrosley May 17, 2026
ecc0b6e
Added some documentation
ChrisCrosley May 17, 2026
1c29728
cleanup, documentation fixes
ChrisCrosley May 17, 2026
582f214
layout builder first pass
ChrisCrosley May 18, 2026
cb5839f
Compile all tools into assembly regardless of layout inclusion
ChrisCrosley May 18, 2026
5abfaeb
Merge branch 'develop' into feature/decouple-layout
ChrisCrosley May 18, 2026
4e00390
Merge Develop
ChrisCrosley May 18, 2026
9b041f6
Save and Reload
ChrisCrosley May 18, 2026
d82de6d
warn missing tools
ChrisCrosley May 18, 2026
0d8a5a3
persist selection on move up/down
ChrisCrosley May 18, 2026
7ca55a8
chore: remove stray Visual Studio WPF temp file
claude May 19, 2026
2295a47
style: apply black formatting to new files
claude May 19, 2026
22484d5
refactor: use existing exts constants for bundle postfixes
claude May 19, 2026
968ce0c
refactor: use coreutils.yaml.dump_dict for layout file writing
claude May 19, 2026
47d88c8
refactor: remove unused imports and dead stack_buffer logic
claude May 19, 2026
aaa02a6
refactor: expose Extension.set_assembly_only_commands
claude May 19, 2026
a0517cd
fix: surface errors instead of swallowing them silently
claude May 19, 2026
e60eb58
refactor: hoist component imports to module top in layout_parser
claude May 19, 2026
ea0045c
docs: trim what-not-why comments and add missing docstring
claude May 19, 2026
27fa973
refactor: rename GetConfigPublic to GetConfigInternal
claude May 19, 2026
28e26a9
style: trim trailing blank lines in layout_cli.py
claude May 19, 2026
adc936a
Merge pull request #2 from ChrisCrosley/claude/pr-checklist-new-featu…
ChrisCrosley May 19, 2026
b5964e8
Added optional layouts folder for alternate layouts to be provided fo…
ChrisCrosley May 19, 2026
6806acf
Merge branch 'feature/decouple-layout' of https://github.com/ChrisCro…
ChrisCrosley May 19, 2026
0a2a62c
reformat
ChrisCrosley May 19, 2026
58980d6
draft icon
ChrisCrosley May 19, 2026
cae658b
Resize Layout Builder icon to 96x96 and add dark mode variant
claude May 19, 2026
fb42eee
Merge pull request #3 from ChrisCrosley/claude/resize-icon-dark-mode-…
ChrisCrosley May 19, 2026
a1359eb
fix: address PR review feedback for layout decoupling
claude May 19, 2026
9b57bd9
refactor: collapse ParseComponents duplication
ChrisCrosley May 19, 2026
c0374a7
Merge pull request #4 from ChrisCrosley/claude/review-pr-feedback-6dJZ9
ChrisCrosley May 19, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extensions/*
**/packages
**/dev/**/bin
**/dev/pyRevitLabs/TestResults/
**/*_wpftmp.csproj

# ignore sphinx build files
docs/_*
Expand Down
Binary file modified bin/netcore/engines/IPY2712PR/pyRevitAssemblyBuilder.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY2712PR/pyRevitExtensionParser.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY2712PR/pyRevitLabs.PyRevit.Runtime.2025.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY2712PR/pyRevitLabs.PyRevit.Runtime.2026.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY2712PR/pyRevitLoader.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY2712PR/pyRevitRunner.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY342/pyRevitAssemblyBuilder.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY342/pyRevitExtensionParser.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY342/pyRevitLabs.PyRevit.Runtime.2025.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY342/pyRevitLabs.PyRevit.Runtime.2026.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY342/pyRevitLoader.dll
Binary file not shown.
Binary file modified bin/netcore/engines/IPY342/pyRevitRunner.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.Common.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.CommonCLI.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.CommonWPF.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.DeffrelDB.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.Emojis.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.Language.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.PyRevit.Runtime.Shared.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.PyRevit.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.TargetApps.AutoCAD.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.TargetApps.Navisworks.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.TargetApps.Revit.dll
Binary file not shown.
Binary file modified bin/netcore/pyRevitLabs.UnitTests.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY2712PR/pyRevitAssemblyBuilder.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY2712PR/pyRevitExtensionParser.dll
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified bin/netfx/engines/IPY2712PR/pyRevitLabs.PyRevit.Runtime.2023.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY2712PR/pyRevitLabs.PyRevit.Runtime.2024.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY2712PR/pyRevitLoader.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY2712PR/pyRevitRunner.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitAssemblyBuilder.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitExtensionParser.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitLabs.PyRevit.Runtime.2017.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitLabs.PyRevit.Runtime.2018.dll
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitLabs.PyRevit.Runtime.2023.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitLabs.PyRevit.Runtime.2024.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitLoader.dll
Binary file not shown.
Binary file modified bin/netfx/engines/IPY342/pyRevitRunner.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.Common.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.CommonCLI.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.CommonWPF.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.DeffrelDB.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.Emojis.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.Language.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.PyRevit.Runtime.Shared.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.PyRevit.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.TargetApps.AutoCAD.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.TargetApps.Navisworks.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.TargetApps.Revit.dll
Binary file not shown.
Binary file modified bin/netfx/pyRevitLabs.UnitTests.dll
Binary file not shown.
4 changes: 4 additions & 0 deletions dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ public static class PyRevitConsts {
public const string ConfigsNewLoaderKey = "new_loader";
public const bool ConfigsNewLoaderDefault = true;

// layout settings
public const string ConfigsDisableCustomLayoutsKey = "disable_custom_layouts";
public const bool ConfigsDisableCustomLayoutsDefault = false;

// theme
public static SolidColorBrush PyRevitAccentBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0xf3, 0x9c, 0x12));
public static SolidColorBrush PyRevitBackgroundBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0x2c, 0x3e, 0x50));
Expand Down
765 changes: 371 additions & 394 deletions dev/pyRevitLoader/pyRevitExtensionParser/ExtensionParser.cs

Large diffs are not rendered by default.

715 changes: 715 additions & 0 deletions dev/pyRevitLoader/pyRevitExtensionParser/LayoutParser.cs

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion dev/pyRevitLoader/pyRevitExtensionParser/ParsedExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ public class ParsedExtension : ParsedComponent
/// </summary>
public List<ExternalLayoutDirective> ExternalLayoutDirectives { get; set; } = new List<ExternalLayoutDirective>();

/// <summary>
/// Tools from the tool index that are NOT referenced in the layout YAML.
/// These are included in assembly generation but NOT shown in the ribbon UI.
/// This ensures all tools have compiled command types available, so adding
/// a tool to the layout later doesn't require a new assembly build.
/// </summary>
public List<ParsedComponent> AssemblyOnlyComponents { get; set; }

/// <summary>
/// Gets or sets the list of users authorized to access this extension.
/// When populated, only users in this list can use the extension.
Expand Down Expand Up @@ -76,6 +84,7 @@ private bool DirExists(string path)

private static readonly HashSet<string> _hashDirSuffixes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".extension",
".tab",
".panel",
".pulldown",
Expand All @@ -93,7 +102,8 @@ private bool DirExists(string path)
private static readonly HashSet<string> _hashDirNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"lib",
"hooks"
"hooks",
"tools"
};

/// <summary>
Expand Down Expand Up @@ -265,6 +275,8 @@ public string StartupScript
/// <summary>
/// Collects all command components from this extension (cached after first call).
/// Builds control IDs for each component based on hierarchy.
/// Also includes assembly-only components (tools not in the layout but still
/// compiled into the DLL so they're available if the layout changes).
/// </summary>
public IEnumerable<ParsedComponent> CollectCommandComponents()
{
Expand All @@ -273,6 +285,14 @@ public IEnumerable<ParsedComponent> CollectCommandComponents()
_cachedCommandComponents = new List<ParsedComponent>();
// Start with no parent control ID - tabs will be the first level
CollectInto(_cachedCommandComponents, this.Children, null);

// Include tools that exist on disk but aren't referenced in the layout.
// They won't appear in the ribbon, but their command types will be
// compiled into the assembly so layout changes don't require a rebuild.
if (AssemblyOnlyComponents != null)
{
CollectInto(_cachedCommandComponents, AssemblyOnlyComponents, null);
}
}
return _cachedCommandComponents;
}
Expand Down
64 changes: 64 additions & 0 deletions dev/pyRevitLoader/pyRevitExtensionParser/PyRevitConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ public string UserLocale
}
}

/// <summary>
/// Gets whether custom layouts are globally disabled.
/// When true, GetCustomLayoutPath always returns null (bundled layouts still apply).
/// </summary>
public bool DisableCustomLayouts
{
get
{
var value = _ini.IniReadValue("core", "disable_custom_layouts");
return TryParseConfigBool(value, out var result) && result;
}
set
{
_ini.IniWriteValue("core", "disable_custom_layouts", value ? TrueString : FalseString);
}
}

/// <summary>
/// Gets or sets whether the new loader architecture is enabled.
/// </summary>
Expand Down Expand Up @@ -607,6 +624,53 @@ public ExtensionConfig ParseExtensionByName(string extensionName)
return null; // Return null if the extension is not found
}

/// <summary>
/// Gets the custom layout file path for an extension, if configured.
/// </summary>
/// <param name="extensionName">Extension name (without .extension suffix)</param>
/// <returns>Path to custom layout file, or null if not configured or file doesn't exist</returns>
public string GetCustomLayoutPath(string extensionName)
{
if (string.IsNullOrEmpty(extensionName))
return null;

// Global toggle: skip custom layouts when disabled
if (DisableCustomLayouts)
return null;

var section = $"{extensionName}.extension";
var value = _ini.IniReadValue(section, "custom_layout_path");

if (string.IsNullOrEmpty(value))
return null;

value = value.Trim();
// Strip quotes if present
if (value.Length >= 2 &&
((value[0] == '"' && value[value.Length - 1] == '"') ||
(value[0] == '\'' && value[value.Length - 1] == '\'')))
{
value = value.Substring(1, value.Length - 2).Trim();
}

// Only return if file exists
return File.Exists(value) ? value : null;
}

/// <summary>
/// Sets or clears the custom layout file path for an extension.
/// </summary>
/// <param name="extensionName">Extension name (without .extension suffix)</param>
/// <param name="layoutPath">Path to custom layout file, or null/empty to clear</param>
public void SetCustomLayoutPath(string extensionName, string layoutPath)
{
if (string.IsNullOrEmpty(extensionName))
return;

var section = $"{extensionName}.extension";
_ini.IniWriteValue(section, "custom_layout_path", layoutPath ?? string.Empty);
}

/// <summary>
/// Parses booleans from INI values (matches Python/json-style and common variants).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Compile Include="ParserLogger.cs" />
<Compile Include="PyRevitConfig.cs" />
<Compile Include="ExtensionParser.cs" />
<Compile Include="LayoutParser.cs" />
<Compile Include="IniFile.cs" />
</ItemGroup>

Expand Down
27 changes: 27 additions & 0 deletions extensions/pyRevitCore.extension/extension_layout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
tabs:
- name: pyRevit
panels:
- name: pyRevit
layout:
- stack:
- Search
- Spy
- Reload
- '>>>'
- About
- Update
- Extensions
- stack:
- Wiki
- Blog
- Repo
- stack:
- apidocs
- emojis
- rpw Docs
- stack:
- Icons8
- RegEx Tool
- Report Bugs
- Settings
- Layout Builder
14 changes: 14 additions & 0 deletions extensions/pyRevitCore.extension/layouts/simple.layout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
tabs:
- name: pyRevit
panels:
- name: pyRevit
layout:
- Search
- '>>>'
- stack:
- About
- Update
- Extensions
- Settings
- Layout Builder

2 changes: 0 additions & 2 deletions extensions/pyRevitCore.extension/pyRevit.tab/bundle.yaml

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading