VTC (View Template Comparer) Enhanced Design Document
Overview
This document outlines the enhanced design for the VTC tool based on stakeholder feedback. The tool enables comparison, selective merging, and conflict resolution of Revit view template settings with a UI that mirrors Revit's native Visibility/Graphics dialog organization.
1. Category Organization - Revit VG Tab Structure
Design Decision
Category organization will exactly mimic Revit's Visibility/Graphics dialog tabs.
Tab Structure
| Tab | CategoryType Filter | Notes |
|---|---|---|
| Model Categories | CategoryType.Model |
Exclude imported categories (Id > 0 with no BuiltInCategory) |
| Annotation Categories | CategoryType.Annotation |
Tags, dimensions, text, symbols |
| Analytical Model Categories | CategoryType.AnalyticalModel |
Structural analytical elements |
| Imported Categories | CategoryType.Model |
Where Category.Id.IntegerValue > 0 and no valid BuiltInCategory |
| Filters | N/A | View filters (ParameterFilterElement) |
| Revit Links | N/A | Linked RVT files - spawns child window |
| Worksets | N/A | Conditional - only if model uses worksets |
| Design Options | N/A | Conditional - only if design options exist |
Implementation
public enum VtcCategoryTab
{
ModelCategories,
AnnotationCategories,
AnalyticalModelCategories,
ImportedCategories,
Filters,
RevitLinks,
Worksets,
DesignOptions
}
public static class VtcCategoryClassifier
{
public static VtcCategoryTab GetTab(Category category)
{
// Check for imported category first (Model type but not built-in)
if (category.CategoryType == CategoryType.Model)
{
var idValue = ElementIdCompat.GetValue(category.Id);
if (idValue > 0 && !IsBuiltInCategory(idValue))
return VtcCategoryTab.ImportedCategories;
return VtcCategoryTab.ModelCategories;
}
return category.CategoryType switch
{
CategoryType.Annotation => VtcCategoryTab.AnnotationCategories,
CategoryType.AnalyticalModel => VtcCategoryTab.AnalyticalModelCategories,
_ => VtcCategoryTab.ModelCategories // Fallback
};
}
private static bool IsBuiltInCategory(long categoryId)
{
return Enum.IsDefined(typeof(BuiltInCategory), (int)categoryId);
}
}
Data Model Changes
// Enhanced category data with tab classification
public sealed record VtcCategoryGraphicsData(
long CategoryId,
string Name,
bool IsVisible,
VtcOverrideGraphicsData Overrides,
VtcCategoryTab Tab); // NEW: Tab classification
// Enhanced tree node with tab grouping
public enum VtcDiffNodeType
{
Root, // Top-level root
Tab, // NEW: VG Tab (Model Categories, Annotation, etc.)
CategoryGroup, // Parent category with subcategories
Setting // Leaf node with actual value
}
2. Containerized Diff View Component
Design Decision
The diff view will be containerized as a reusable UserControl so it can be embedded in:
- The main VTC window
- Child windows for Linked File overrides
- Future comparison contexts
Architecture
VtcDiffViewControl (UserControl)
├── CategoryTabControl (TabControl)
│ ├── Model Categories Tab
│ ├── Annotation Categories Tab
│ ├── Analytical Model Categories Tab
│ ├── Imported Categories Tab
│ └── Filters Tab
├── Side-by-side TreeViews (Left/Right)
├── Selection Controls
└── Action Bar (Apply/Merge buttons)
Child Window Pattern for Linked Files
public sealed class VtcLinkedFileWindow : DbtWindowBase
{
// Reuses the same VtcDiffViewControl
public VtcLinkedFileWindow(
RevitLinkInstance linkInstance,
VtcTemplateSettingsModel leftTemplate,
VtcTemplateSettingsModel rightTemplate)
{
Title = $"Linked File Overrides: {linkInstance.Name}";
Content = new VtcDiffViewControl
{
DataContext = new VtcLinkedFileDiffViewModel(
linkInstance, leftTemplate, rightTemplate)
};
}
}
Component Interface
public interface IVtcDiffViewHost
{
/// <summary>
/// The comparison result to display.
/// </summary>
VtcComparisonResult Comparison { get; }
/// <summary>
/// Tree roots organized by VG tab structure.
/// </summary>
ObservableCollection<VtcDiffTreeNode> DiffTreeRoots { get; }
/// <summary>
/// Currently selected tab.
/// </summary>
VtcCategoryTab SelectedTab { get; set; }
/// <summary>
/// Collect all current selections.
/// </summary>
IReadOnlyCollection<VtcSelection> CollectSelections();
/// <summary>
/// Apply selections and return conflict report.
/// </summary>
VtcApplyResult ApplySelections(VtcTemplateSide targetSide);
}
3. Selection UI - Single and Nested Selection
Design Decision
Provide dual-action selection controls for both single-item and cascading (single + all nested) selection/deselection.
UI Pattern: Dual Checkbox Column
┌─────────────────────────────────────────────────────────────────┐
│ Setting │ Sel │ All │ Value (Template A) │
├─────────────────────────────────────────────────────────────────┤
│ ▼ Structural Framing │ [☑] │ [☑] │ │
│ ├─ Visibility │ [☑] │ │ Visible │
│ ├─ Line Color │ [☑] │ │ RGB(0,0,255) │
│ └─ Line Weight │ [ ] │ │ 3 │
│ ▼ Walls │ [ ] │ [ ] │ │
│ ├─ Visibility │ [ ] │ │ Visible │
│ └─ Cut Pattern │ [ ] │ │ Solid Fill │
└─────────────────────────────────────────────────────────────────┘
Legend:
[Sel] = Select this single item only
[All] = Select this item AND all nested children (only shown for parent nodes)
Data Model Extension
public sealed class VtcDiffTreeNode : INotifyPropertyChanged
{
// Existing properties...
/// <summary>
/// Select/deselect this single node only.
/// </summary>
public bool SelectLeft { get; set; }
public bool SelectRight { get; set; }
/// <summary>
/// Select/deselect this node AND all descendants.
/// Only applicable to non-leaf nodes (Root, Tab, CategoryGroup).
/// </summary>
public bool SelectLeftWithChildren
{
get => _selectLeftWithChildren;
set
{
_selectLeftWithChildren = value;
if (value)
PropagateSelectionToChildren(VtcTemplateSide.Left, true);
OnPropertyChanged();
}
}
public bool SelectRightWithChildren
{
get => _selectRightWithChildren;
set
{
_selectRightWithChildren = value;
if (value)
PropagateSelectionToChildren(VtcTemplateSide.Right, true);
OnPropertyChanged();
}
}
/// <summary>
/// Shows whether this node can cascade selection (has children).
/// </summary>
public bool CanCascadeSelection => Children.Count > 0;
private void PropagateSelectionToChildren(VtcTemplateSide side, bool selected)
{
foreach (var child in Children)
{
if (side == VtcTemplateSide.Left)
child.SelectLeft = selected;
else
child.SelectRight = selected;
child.PropagateSelectionToChildren(side, selected);
}
}
}
Alternative UI Consideration: Context Menu
For cleaner UI, offer right-click context menu:
Right-click on "Structural Framing" →
┌─────────────────────────────────┐
│ ☑ Select (Template A) │
│ ☐ Select (Template B) │
├─────────────────────────────────┤
│ ☑ Select All Nested (A) │
│ ☐ Select All Nested (B) │
├─────────────────────────────────┤
│ Clear Selection │
│ Clear All Nested │
└─────────────────────────────────┘
Recommended Approach: Split Button
A split button provides the cleanest UX:
┌───────────────────────────────────────────────────────────────────┐
│ Setting │ Use A │ Value │
├───────────────────────────────────────────────────────────────────┤
│ ▼ Structural Framing │ [☑ ▾] │ │
│ │ ├─ Select │ │
│ │ └─ Select All │ │
└───────────────────────────────────────────────────────────────────┘
The checkbox selects the single item; clicking the dropdown arrow shows "Select" / "Select All Children" options.
4. Merge Strategy - Disk-Based Workflow
Design Decision
- Option A: Write changes to disk (JSON snapshots)
- Create merged templates as new elements
- Update existing templates based on selected properties
Workflow
┌─────────────────────────────────────────────────────────────────┐
│ MERGE WORKFLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. COMPARE │
│ ├─ Extract Template A settings → VtcTemplateSettingsModel │
│ ├─ Extract Template B settings → VtcTemplateSettingsModel │
│ └─ Generate diff tree with tab organization │
│ │
│ 2. SELECT │
│ ├─ User picks settings from A or B per property │
│ ├─ Single or cascading selection │
│ └─ Selections stored in VtcSelection list │
│ │
│ 3. PREVIEW (Optional) │
│ └─ Show merged result before applying │
│ │
│ 4. APPLY │
│ ├─ Option: Apply to Template A (in-place update) │
│ ├─ Option: Apply to Template B (in-place update) │
│ └─ Option: Create New Template (merged copy) │
│ │
│ 5. PERSIST (Optional) │
│ ├─ Save merged template snapshot to JSON │
│ └─ Store in Template Library for future use │
│ │
└─────────────────────────────────────────────────────────────────┘
Merge Operation Types
public enum VtcMergeOperation
{
/// <summary>
/// Update Template A with selected settings from comparison.
/// </summary>
ApplyToLeft,
/// <summary>
/// Update Template B with selected settings from comparison.
/// </summary>
ApplyToRight,
/// <summary>
/// Create a new template by duplicating base and applying selections.
/// </summary>
CreateMerged,
/// <summary>
/// Export merged settings to JSON file (no Revit changes).
/// </summary>
ExportToJson
}
5. Conflict Handling and Apply Workflow
Design Decision
- Attempt all selected changes
- Report conflicts after apply attempt
- Retain selections (don't clear on partial failure)
- Highlight problematic selections
- Filter control to show/hide conflicts (hidden until apply attempted)
Conflict Types
public enum VtcConflictType
{
/// <summary>
/// No conflict - change applied successfully.
/// </summary>
None,
/// <summary>
/// Category/Filter doesn't exist in target document.
/// </summary>
ElementNotFound,
/// <summary>
/// Parameter is read-only or controlled by another template.
/// </summary>
ReadOnlyParameter,
/// <summary>
/// Value format incompatible (e.g., pattern ID doesn't exist).
/// </summary>
IncompatibleValue,
/// <summary>
/// Revit API threw an exception during apply.
/// </summary>
ApiError
}
public sealed record VtcConflictInfo(
string SettingKey,
VtcConflictType ConflictType,
string Message,
Exception? Exception);
public sealed record VtcApplyResult(
int TotalAttempted,
int SuccessCount,
int FailedCount,
IReadOnlyList<VtcConflictInfo> Conflicts);
Enhanced Tree Node with Conflict State
public sealed class VtcDiffTreeNode : INotifyPropertyChanged
{
// Existing properties...
/// <summary>
/// Conflict information after apply attempt. Null if not yet attempted or no conflict.
/// </summary>
public VtcConflictInfo? Conflict { get; set; }
/// <summary>
/// Whether this node has a conflict after apply.
/// </summary>
public bool HasConflict => Conflict != null;
/// <summary>
/// Whether apply has been attempted on this node.
/// </summary>
public bool ApplyAttempted { get; set; }
}
UI State Machine
┌─────────────────────────────────────────────────────────────────┐
│ UI STATE MACHINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ State: INITIAL │
│ ├─ Conflict filter: HIDDEN │
│ ├─ Apply button: ENABLED │
│ └─ Conflict highlights: NONE │
│ │
│ ──[User clicks Apply]──────────────────────────────────────────│
│ │
│ State: APPLYING │
│ ├─ Conflict filter: HIDDEN │
│ ├─ Apply button: DISABLED (with spinner) │
│ └─ Progress indicator shown │
│ │
│ ──[Apply completes]────────────────────────────────────────────│
│ │
│ State: POST_APPLY (if any conflicts) │
│ ├─ Conflict filter: VISIBLE │
│ │ ├─ [Show All] │
│ │ ├─ [Show Conflicts Only] │
│ │ └─ [Hide Conflicts] │
│ ├─ Apply button: ENABLED (for retry) │
│ ├─ Conflict highlights: ACTIVE │
│ │ ├─ Red border on conflicting rows │
│ │ ├─ Warning icon with tooltip │
│ │ └─ Conflict count badge │
│ └─ Selections: RETAINED (user can modify and retry) │
│ │
│ State: POST_APPLY (no conflicts) │
│ ├─ Conflict filter: HIDDEN │
│ ├─ Success message shown │
│ └─ Selections: CLEARED or RETAINED (configurable) │
│ │
└─────────────────────────────────────────────────────────────────┘
Conflict Filter Control
<!-- Only visible after apply has been attempted AND conflicts exist -->
<StackPanel Orientation="Horizontal"
Visibility="{Binding ShowConflictFilter, Converter={StaticResource BoolToVisibility}}">
<TextBlock Text="Filter:" Margin="0,0,8,0" VerticalAlignment="Center" />
<RadioButton Content="All"
IsChecked="{Binding ConflictFilter, Converter={StaticResource EnumToBool},
ConverterParameter=All}"
GroupName="ConflictFilter" />
<RadioButton Content="Conflicts Only"
IsChecked="{Binding ConflictFilter, Converter={StaticResource EnumToBool},
ConverterParameter=ConflictsOnly}"
GroupName="ConflictFilter" />
<RadioButton Content="Successful Only"
IsChecked="{Binding ConflictFilter, Converter={StaticResource EnumToBool},
ConverterParameter=SuccessfulOnly}"
GroupName="ConflictFilter" />
<!-- Conflict count badge -->
<Border Background="{DynamicResource Error}" CornerRadius="10"
Padding="6,2" Margin="12,0,0,0"
Visibility="{Binding HasConflicts, Converter={StaticResource BoolToVisibility}}">
<TextBlock Text="{Binding ConflictCount, StringFormat='{0} conflicts'}"
Foreground="White" FontSize="11" />
</Border>
</StackPanel>
Conflict Highlighting in TreeView
<!-- Enhanced tree node template with conflict indication -->
<Style x:Key="Vtc.ConflictBorder" TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasConflict}" Value="True">
<Setter Property="BorderBrush" Value="{DynamicResource Error}" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Background" Value="{DynamicResource ErrorBackground}" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Conflict warning icon -->
<TextBlock x:Name="ConflictIcon"
FontFamily="Segoe MDL2 Assets"
Text=""
Foreground="{DynamicResource Error}"
ToolTip="{Binding Conflict.Message}"
Visibility="{Binding HasConflict, Converter={StaticResource BoolToVisibility}}" />
6. Complete ViewModel Structure
public sealed partial class VtcWindowViewModel : ObservableObject, IVtcDiffViewHost
{
// Template selection
[ObservableProperty] private VtcTemplateOption? _selectedLeft;
[ObservableProperty] private VtcTemplateOption? _selectedRight;
// Tab navigation (mirrors Revit VG tabs)
[ObservableProperty] private VtcCategoryTab _selectedTab = VtcCategoryTab.ModelCategories;
public ObservableCollection<VtcCategoryTab> AvailableTabs { get; }
// Diff tree organized by tabs
public ObservableCollection<VtcDiffTreeNode> DiffTreeRoots { get; }
// Conflict management (hidden until apply attempted)
[ObservableProperty] private bool _applyAttempted;
[ObservableProperty] private VtcConflictFilter _conflictFilter = VtcConflictFilter.All;
[ObservableProperty] private VtcApplyResult? _lastApplyResult;
public bool ShowConflictFilter => ApplyAttempted && (LastApplyResult?.FailedCount ?? 0) > 0;
public bool HasConflicts => (LastApplyResult?.FailedCount ?? 0) > 0;
public int ConflictCount => LastApplyResult?.FailedCount ?? 0;
// Linked files (spawns child windows)
public ObservableCollection<VtcLinkedFileInfo> LinkedFiles { get; }
// Commands
[RelayCommand] private void ApplyToLeft() => ApplyWithConflictHandling(VtcTemplateSide.Left);
[RelayCommand] private void ApplyToRight() => ApplyWithConflictHandling(VtcTemplateSide.Right);
[RelayCommand] private void OpenLinkedFileOverrides(VtcLinkedFileInfo link) => SpawnLinkedFileWindow(link);
[RelayCommand] private void SelectAllInTab() => SelectAllNodesInCurrentTab(true);
[RelayCommand] private void DeselectAllInTab() => SelectAllNodesInCurrentTab(false);
private void ApplyWithConflictHandling(VtcTemplateSide targetSide)
{
var selections = CollectSelections();
var result = _comparer.ApplySelectionsWithReport(target, _comparison!, selections);
ApplyAttempted = true;
LastApplyResult = result;
// Update tree nodes with conflict info
foreach (var conflict in result.Conflicts)
{
var node = FindNodeByKey(conflict.SettingKey);
if (node != null)
{
node.Conflict = conflict;
node.ApplyAttempted = true;
}
}
// Mark successful nodes
foreach (var selection in selections)
{
if (!result.Conflicts.Any(c => c.SettingKey == selection.Key))
{
var node = FindNodeByKey(selection.Key);
if (node != null)
{
node.Conflict = null;
node.ApplyAttempted = true;
}
}
}
OnPropertyChanged(nameof(ShowConflictFilter));
OnPropertyChanged(nameof(HasConflicts));
OnPropertyChanged(nameof(ConflictCount));
ShowApplyResultSummary(result);
}
}
public enum VtcConflictFilter
{
All,
ConflictsOnly,
SuccessfulOnly
}
7. File Structure After Implementation
VTC/
├── Documentation/
│ └── VTC_Enhanced_Design.md # This document
├── Contracts/
│ ├── VtcDataModels.cs # Enhanced with Tab classification
│ ├── VtcEnums.cs # Add VtcCategoryTab, VtcConflictType
│ ├── IVtcComparisonService.cs # Add ApplySelectionsWithReport
│ └── VtcConflictModels.cs # NEW: Conflict handling types
├── UI/
│ ├── Controls/
│ │ └── VtcDiffViewControl.xaml # NEW: Reusable diff view control
│ ├── ViewModels/
│ │ ├── VtcWindowViewModel.cs # Enhanced with conflict handling
│ │ ├── VtcDiffTreeNode.cs # Enhanced with conflict state
│ │ ├── VtcDiffTreeBuilder.cs # Enhanced with tab organization
│ │ ├── VtcLinkedFileDiffViewModel.cs # NEW: For child windows
│ │ └── VtcCategoryClassifier.cs # NEW: Tab classification logic
│ ├── Views/
│ │ ├── VtcWindow.xaml # Main window (uses VtcDiffViewControl)
│ │ └── VtcLinkedFileWindow.xaml # NEW: Child window for linked files
│ ├── Converters/
│ │ ├── VtcDiffBackgroundConverter.cs
│ │ ├── VtcConflictToVisibilityConverter.cs # NEW
│ │ └── VtcCategoryTabToIconConverter.cs # NEW
│ └── Behaviors/
│ └── ScrollSyncBehavior.cs
├── Revit/
│ └── Services/
│ ├── VtcComparisonService.cs # Enhanced with conflict reporting
│ └── VtcCategoryClassifier.cs # NEW: Revit-specific classification
└── Services/
└── VtcTemplateStorageService.cs
8. Implementation Priority
| Priority | Feature | Complexity | Dependencies |
|---|---|---|---|
| 1 | Tab-based category organization | Medium | VtcCategoryClassifier |
| 2 | Dual selection (single/nested) | Medium | VtcDiffTreeNode changes |
| 3 | Conflict handling workflow | High | Apply result reporting |
| 4 | Containerized diff view control | Medium | Extract from VtcWindow |
| 5 | Linked file child windows | Medium | VtcDiffViewControl |
| 6 | Conflict filter UI | Low | After conflict handling |
9. Linked File Extraction (RevitLinkType Overrides)
Design Decision
Extract and display RevitLinkType visibility overrides from view templates, enabling full comparison and merging of linked file settings.
Revit API Integration
/// <summary>
/// Data model for linked file visibility overrides within a view template.
/// </summary>
public sealed record VtcLinkedFileOverrideData(
long LinkTypeId,
string LinkName,
string FilePath,
bool IsVisible,
VtcLinkedFileVisibility LinkedViewId, // None, ByHostView, or specific view ID
IReadOnlyList<VtcCategoryGraphicsData> CategoryOverrides);
public enum VtcLinkedFileVisibility
{
None, // Link hidden
ByHostView, // Use host view's VG settings
Custom // Uses stored view ID for overrides
}
Extraction Implementation
public List<VtcLinkedFileOverrideData> ExtractLinkedFileOverrides(View template)
{
var results = new List<VtcLinkedFileOverrideData>();
// Get all RevitLinkType elements in the document
var linkTypes = new FilteredElementCollector(_document)
.OfClass(typeof(RevitLinkType))
.Cast<RevitLinkType>();
foreach (var linkType in linkTypes)
{
var linkId = linkType.Id;
// Get visibility state from template
var isVisible = !template.GetCategoryHidden(linkId);
// Get linked view override (determines how link displays)
var linkedViewId = template.GetLinkedOverrideViewId(linkId);
var visibility = linkedViewId == ElementId.InvalidElementId
? VtcLinkedFileVisibility.ByHostView
: VtcLinkedFileVisibility.Custom;
// Extract category-level overrides for this link
var categoryOverrides = ExtractLinkedCategoryOverrides(template, linkType);
results.Add(new VtcLinkedFileOverrideData(
ElementIdCompat.GetValue(linkId),
linkType.Name,
linkType.GetExternalResourceReference()?.GetReferenceInformation()?.GetPath() ?? string.Empty,
isVisible,
visibility,
categoryOverrides));
}
return results;
}
10. Worksets & Design Options Support
Design Decision
Conditionally display Worksets and Design Options tabs when the model contains these features.
Detection Logic
public bool HasWorksets(Document doc) => doc.IsWorkshared;
public bool HasDesignOptions(Document doc)
{
return new FilteredElementCollector(doc)
.OfClass(typeof(DesignOption))
.Any();
}
Data Models
/// <summary>
/// Workset visibility override data.
/// </summary>
public sealed record VtcWorksetOverrideData(
long WorksetId,
string Name,
WorksetVisibility Visibility); // Visible, Hidden, UseGlobalSetting
/// <summary>
/// Design option visibility override data.
/// </summary>
public sealed record VtcDesignOptionOverrideData(
long DesignOptionId,
string Name,
string SetName, // Parent design option set
bool IsActive);
Tab Visibility Control
// In VtcWindowViewModel
public bool ShowWorksetsTab => _hasWorksets;
public bool ShowDesignOptionsTab => _hasDesignOptions;
// Initialize during comparison
private void CheckConditionalTabs()
{
_hasWorksets = _documentInfo.IsWorkshared;
_hasDesignOptions = new FilteredElementCollector(_document)
.OfClass(typeof(DesignOption))
.Any();
OnPropertyChanged(nameof(ShowWorksetsTab));
OnPropertyChanged(nameof(ShowDesignOptionsTab));
}
11. Search/Filter by Name
Design Decision
Provide a text search box that filters settings by name across all categories in real-time.
UI Layout
┌─────────────────────────────────────────────────────────────────┐
│ [🔍] Search settings... [Clear] [×] │
├─────────────────────────────────────────────────────────────────┤
│ ▼ Model Categories (3 matches) │
│ ├─ Structural Framing [☑] Visible [☐] Hidden │
│ └─ Structural Columns [☑] Visible [☐] Hidden │
│ ▼ Annotation Categories (1 match) │
│ └─ Structural Framing Tags [☑] Visible [☐] Visible │
└─────────────────────────────────────────────────────────────────┘
Implementation
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FilteredDiffTreeRoots))]
private string _searchText = string.Empty;
public IEnumerable<VtcDiffTreeNode> FilteredDiffTreeRoots
{
get
{
var roots = GetConflictFilteredRoots();
if (string.IsNullOrWhiteSpace(SearchText))
return roots;
return roots
.Select(root => FilterNodeBySearch(root, SearchText))
.Where(node => node != null)!;
}
}
private static VtcDiffTreeNode? FilterNodeBySearch(VtcDiffTreeNode node, string searchText)
{
// Check if this node's name matches
var nameMatches = node.DisplayName.Contains(searchText, StringComparison.OrdinalIgnoreCase);
// Filter children recursively
var filteredChildren = node.Children
.Select(c => FilterNodeBySearch(c, searchText))
.Where(c => c != null)
.ToList();
// Include node if name matches OR has matching children
if (!nameMatches && filteredChildren.Count == 0)
return null;
// Create filtered copy
var filteredNode = new VtcDiffTreeNode(node.DisplayName, node.NodeType)
{
Change = node.Change,
CategoryTab = node.CategoryTab
};
foreach (var child in filteredChildren)
filteredNode.Children.Add(child!);
filteredNode.IsExpanded = true; // Auto-expand when filtering
return filteredNode;
}
[RelayCommand]
private void ClearSearch() => SearchText = string.Empty;
12. Copy Linked File Overrides
Design Decision
Enable copying visibility overrides from one linked file to another within the same view template.
Workflow
┌─────────────────────────────────────────────────────────────────┐
│ Copy Linked File Overrides │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Source Link: [▼ Architectural.rvt ] │
│ │
│ Target Link: [▼ Structural.rvt ] │
│ │
│ Options: │
│ [☑] Copy visibility state │
│ [☑] Copy category overrides │
│ [☑] Copy linked view reference │
│ │
│ [Cancel] [Copy Overrides] │
└─────────────────────────────────────────────────────────────────┘
Implementation
public sealed record VtcCopyLinkedOverridesOptions(
long SourceLinkTypeId,
long TargetLinkTypeId,
bool CopyVisibility,
bool CopyCategoryOverrides,
bool CopyLinkedViewReference);
public void CopyLinkedFileOverrides(View template, VtcCopyLinkedOverridesOptions options)
{
var sourceId = new ElementId(options.SourceLinkTypeId);
var targetId = new ElementId(options.TargetLinkTypeId);
using var tx = new Transaction(_document, "Copy Linked File Overrides");
tx.Start();
if (options.CopyVisibility)
{
var isHidden = template.GetCategoryHidden(sourceId);
template.SetCategoryHidden(targetId, isHidden);
}
if (options.CopyCategoryOverrides)
{
var ogs = template.GetCategoryOverrides(sourceId);
template.SetCategoryOverrides(targetId, ogs);
}
if (options.CopyLinkedViewReference)
{
var linkedViewId = template.GetLinkedOverrideViewId(sourceId);
template.SetLinkedOverrideViewId(targetId, linkedViewId);
}
tx.Commit();
}
13. Interactive Diff View with Inline Override Controls
Design Decision
Mirror Revit's Visibility/Graphics dialog by providing inline controls for each category row, allowing direct editing of override settings.
Row Control Layout
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Category Name │ Projection/Surface │ Cut │ HT │ Detail │
│ │ [Lines] [Patterns] [Trans] │ [Lines] [Pat] │ [☐]│ [▼ Fine ] │
├─────────────────────────────────────────────────────────────────────────────────┤
│ Structural │ [⚫] [◻] [50% ] │ [⚫] [◻] │ [☐]│ [▼ Med ] │
│ Framing │ │ │ │ │
└─────────────────────────────────────────────────────────────────────────────────┘
Legend:
[⚫] = Lines button (opens Line Settings dialog)
[◻] = Patterns button (opens Fill Pattern dialog)
[50%] = Transparency button (opens Transparency slider dialog)
[☐] = Halftone checkbox (direct toggle)
[▼] = Detail Level dropdown (Coarse/Medium/Fine)
Control Types
| Control | Type | Dialog |
|---|---|---|
| Projection Lines | Button | Line Settings Dialog |
| Projection Patterns | Button | Fill Pattern Dialog |
| Transparency | Button | Transparency Slider Dialog |
| Cut Lines | Button | Line Settings Dialog |
| Cut Patterns | Button | Fill Pattern Dialog |
| Halftone | Checkbox | Inline (no dialog) |
| Detail Level | ComboBox | Inline dropdown |
14. Override Editing Dialogs
14.1 Line Settings Dialog
A compact modal for configuring line overrides.
┌─────────────────────────────────────────────────┐
│ Line Settings [×] │
├─────────────────────────────────────────────────┤
│ │
│ Preview: ──────────────────────── │
│ (live preview of current settings) │
│ │
│ Weight: [▼ 3 ] │
│ │
│ Color: [■ Blue ] [🎨] │
│ │
│ Pattern: [▼ Solid ] │
│ │
│ [Reset to Default] │
│ │
│ [Cancel] [Apply] │
└─────────────────────────────────────────────────┘
Data Model:
public sealed record VtcLineSettingsData
{
public int? LineWeight { get; init; }
public VtcColorData? LineColor { get; init; }
public long? LinePatternId { get; init; }
public string? LinePatternName { get; init; } // For display
}
14.2 Fill Pattern Dialog
A compact modal for configuring foreground/background fill patterns.
┌─────────────────────────────────────────────────┐
│ Fill Pattern Settings [×] │
├─────────────────────────────────────────────────┤
│ │
│ Preview: ┌──────────────────┐ │
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │
│ └──────────────────┘ │
│ │
│ ─── Foreground ─── │
│ [☑] Visible │
│ Pattern: [▼ Crosshatch ] │
│ Color: [■ Gray ] [🎨] │
│ │
│ ─── Background ─── │
│ [☑] Visible │
│ Pattern: [▼ Solid Fill ] │
│ Color: [■ White ] [🎨] │
│ │
│ [Reset to Default] │
│ │
│ [Cancel] [Apply] │
└─────────────────────────────────────────────────┘
Data Model:
public sealed record VtcFillPatternData
{
public bool ForegroundVisible { get; init; }
public long? ForegroundPatternId { get; init; }
public string? ForegroundPatternName { get; init; }
public VtcColorData? ForegroundColor { get; init; }
public bool BackgroundVisible { get; init; }
public long? BackgroundPatternId { get; init; }
public string? BackgroundPatternName { get; init; }
public VtcColorData? BackgroundColor { get; init; }
}
14.3 Transparency Dialog
A minimal modal with just a slider control.
┌─────────────────────────────────────────────────┐
│ Transparency [×] │
├─────────────────────────────────────────────────┤
│ │
│ Preview: [░░░░░░░░░░░░░░░░] 50% │
│ │
│ [────────────●────────────] 0 50 100 │
│ │
│ [Cancel] [Apply] │
└─────────────────────────────────────────────────┘
Data Model:
public sealed record VtcTransparencyData(int Transparency); // 0-100
15. Dialog Implementation with AlertService
Common Dialog Pattern
public sealed class VtcLineSettingsBodyViewModel : ObservableObject, IAlertBody
{
private int? _lineWeight;
private VtcColorData? _lineColor;
private long? _linePatternId;
public VtcLineSettingsBodyViewModel(
VtcLineSettingsData initialData,
IReadOnlyList<VtcLinePatternInfo> availablePatterns,
IReadOnlyList<int> availableWeights)
{
_lineWeight = initialData?.LineWeight;
_lineColor = initialData?.LineColor;
_linePatternId = initialData?.LinePatternId;
AvailablePatterns = availablePatterns;
AvailableWeights = availableWeights;
}
public int? LineWeight
{
get => _lineWeight;
set => SetProperty(ref _lineWeight, value);
}
public VtcColorData? LineColor
{
get => _lineColor;
set => SetProperty(ref _lineColor, value);
}
public long? LinePatternId
{
get => _linePatternId;
set => SetProperty(ref _linePatternId, value);
}
public IReadOnlyList<VtcLinePatternInfo> AvailablePatterns { get; }
public IReadOnlyList<int> AvailableWeights { get; }
// IAlertBody implementation
public bool IsValid => true; // Always valid, changes are optional
public event EventHandler? ValidityChanged;
public object? GetResult() => new VtcLineSettingsData
{
LineWeight = LineWeight,
LineColor = LineColor,
LinePatternId = LinePatternId
};
}
Dialog Invocation
public VtcLineSettingsData? ShowLineSettingsDialog(
VtcLineSettingsData? current,
string settingName)
{
var body = new VtcLineSettingsBodyViewModel(
current,
GetAvailableLinePatterns(),
GetAvailableLineWeights());
var request = new AlertRequest($"Line Settings: {settingName}", body)
{
Variant = AlertVariant.Info,
Options = new AlertWindowOptions
{
Width = 350,
Height = 280,
MinWidth = 300,
MinHeight = 250
},
Buttons = new[]
{
new AlertButtonSpec("cancel", "Cancel") { IsCancel = true },
new AlertButtonSpec("apply", "Apply") { IsDefault = true, SetDialogResult = true }
}
};
var result = _alerts.Show(request);
return result.ClickedButtonId == "apply"
? result.Payload as VtcLineSettingsData
: null;
}
16. Visual Preview Components
Line Preview Control
<!-- Preview showing line weight, color, and pattern -->
<Canvas Height="20" Width="200" ClipToBounds="True">
<Line X1="10" Y1="10" X2="190" Y2="10"
Stroke="{Binding PreviewBrush}"
StrokeThickness="{Binding LineWeight}"
StrokeDashArray="{Binding PatternDashes}" />
</Canvas>
Fill Pattern Preview Control
<!-- Preview showing foreground over background pattern -->
<Grid Width="100" Height="60">
<!-- Background pattern -->
<Rectangle Fill="{Binding BackgroundBrush}"
Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}" />
<!-- Foreground pattern overlay -->
<Rectangle Fill="{Binding ForegroundBrush}"
Visibility="{Binding ForegroundVisible, Converter={StaticResource BoolToVis}}" />
</Grid>
Transparency Preview Control
<!-- Checkerboard background with semi-transparent overlay -->
<Grid Width="100" Height="30">
<Rectangle>
<Rectangle.Fill>
<DrawingBrush TileMode="Tile" Viewport="0,0,10,10" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="LightGray">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,5,5" />
<RectangleGeometry Rect="5,5,5,5" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Fill="{DynamicResource Primary}"
Opacity="{Binding TransparencyDecimal}" />
</Grid>
17. Updated File Structure
VTC/
├── Documentation/
│ └── VTC_Enhanced_Design.md # This document
├── Contracts/
│ ├── VtcDataModels.cs # Enhanced with linked files, worksets, design options
│ ├── VtcEnums.cs # Add VtcLinkedFileVisibility
│ ├── IVtcComparisonService.cs
│ └── VtcOverrideEditingModels.cs # NEW: Line, pattern, transparency data
├── UI/
│ ├── Controls/
│ │ ├── VtcDiffViewControl.xaml # Enhanced with search and inline controls
│ │ ├── VtcLinePreviewControl.xaml # NEW: Line preview
│ │ ├── VtcPatternPreviewControl.xaml # NEW: Pattern preview
│ │ └── VtcTransparencyPreviewControl.xaml # NEW: Transparency preview
│ ├── ViewModels/
│ │ ├── VtcWindowViewModel.cs # Enhanced with search, conditional tabs
│ │ ├── VtcDiffTreeNode.cs # Enhanced with edit commands
│ │ ├── VtcDiffTreeBuilder.cs # Enhanced with linked files, worksets
│ │ ├── VtcLinkedFileDiffViewModel.cs
│ │ ├── VtcLineSettingsBodyViewModel.cs # NEW: Line dialog body
│ │ ├── VtcFillPatternBodyViewModel.cs # NEW: Pattern dialog body
│ │ └── VtcTransparencyBodyViewModel.cs # NEW: Transparency dialog body
│ ├── Views/
│ │ ├── VtcWindow.xaml # Main window with search
│ │ ├── VtcLinkedFileWindow.xaml
│ │ └── VtcCopyLinkedOverridesWindow.xaml # NEW: Copy dialog
│ ├── Converters/
│ │ ├── VtcDiffBackgroundConverter.cs
│ │ ├── VtcEnumToBoolConverter.cs
│ │ └── VtcPatternToBrushConverter.cs # NEW: Pattern preview
│ └── Behaviors/
│ └── ScrollSyncBehavior.cs
├── Revit/
│ └── Services/
│ ├── VtcComparisonService.cs # Enhanced with linked files, worksets, design options
│ └── VtcCategoryClassifier.cs
└── Services/
└── VtcTemplateStorageService.cs
18. Implementation Priority (Updated)
| Priority | Feature | Complexity | Dependencies |
|---|---|---|---|
| 1 | Search/Filter by Name | Low | None |
| 2 | Linked File Extraction | High | Revit API integration |
| 3 | Worksets & Design Options | Medium | Conditional tab visibility |
| 4 | Copy Linked File Overrides | Medium | Linked file extraction |
| 5 | Inline Override Controls | High | All dialogs |
| 6 | Line Settings Dialog | Medium | Preview controls |
| 7 | Fill Pattern Dialog | Medium | Preview controls |
| 8 | Transparency Dialog | Low | Preview control |