I made this popup blocker with ai. It handles the white popups in the bottom right corner and puts the info from them into a separate log window. Speeds up workflow with less interruptions.
import clr
import os
import System
import time
import subprocess
# Add Revit API references
clr.AddReference('RevitAPI')
clr.AddReference('RevitAPIUI')
clr.AddReference('RevitServices')
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")
# Import necessary classes from Revit API
from Autodesk.Revit.DB.Events import FailuresProcessingEventArgs, DocumentChangedEventArgs
from Autodesk.Revit.UI.Events import DialogBoxShowingEventArgs
from Autodesk.Revit.DB import (
FailureProcessingResult, FailuresAccessor, FailureSeverity,
BuiltInFailures, FailureResolutionType, FailureDefinitionId
)
from Autodesk.Revit.UI import UIApplication, TaskDialog, TaskDialogResult
from System.Collections.Generic import List
from System import Guid
from System.Windows.Forms import Form, TextBox, Button, DockStyle, ScrollBars, FormBorderStyle, BorderStyle, Panel
from System.Drawing import Point, Size, Color, Font, FontStyle, ContentAlignment, FontFamily
# Get the current Revit application and document context
uiapp = __revit__
app = uiapp.Application
doc = __revit__.ActiveUIDocument.Document
# DELETE INSTANCE FAILURE GUIDS
DELETE_INSTANCE_GUIDS = [
Guid("c4c5d448-87fb-4622-acff-fdb957172dda"), # "Instance(s) of ... not cutting anything"
Guid("62f9582a-2459-495d-ba79-23ae8d0753e1"), # "Can't cut instance(s) ... out of its host"
Guid("6650b20b-592d-41a0-9f52-51f5f26e3fc3"),
Guid("36113e20-98f1-4c51-9ace-39cbee1e6c8f"), # "Can't cut instance(s) ... out of its host"
Guid("74441dd6-e6dd-41ea-a57c-04f4a4957f19"),
Guid("16f579ea-6f84-4989-95d1-007c3bdb2f73"),
Guid("6650b20b-592d-41a0-9f52-51f5f26e3fc3"),
Guid("c31ef50f-fc52-49f1-8b3a-aff5fab42f1a"),
Guid("b44c8ba0-7a86-44c1-bbf1-2de8e2017266"),
Guid("8a9ff20d-fdc2-4f98-87e6-2aa8b71b0c83"),
Guid("b00a7be7-d0b4-47e6-88c4-eeada3021131"),
Guid("34a653ae-4577-4bc4-ae55-e2df7f5cfa9c"),
Guid("76297875-79c8-451b-8f9e-3eed349ac3ba"),
Guid("88500be4-b871-448c-b406-afd488c3580d"),
Guid("8e5a1b64-2345-40cf-aa3b-523e5e42700e"),
Guid("4749d31a-cb87-4536-9637-b53dd96f3722"),
Guid("c3e30bdc-bfc1-4ff4-a8cf-2aa55c020350"),
# Add more GUIDs as needed from your log
]
# Save prompt dialog IDs - DO NOT SUPPRESS THESE
save_prompt_ids = [
"TaskDialog_Close_Project", # Save prompt when closing project
"TaskDialog_Changes_Not_Saved", # Generic unsaved changes prompt
"TaskDialog_Exit_Without_Saving", # Exit without saving prompt
"TaskDialog_Save_File", # Save prompt when closing file
"TaskDialog_Model_Upgrade" # Prompt to upgrade model.
"TaskDialog_Stop_Operation" # Interrupt/Cancel
"TaskDialog_Family_Already_Exists"
]
class LogWindow(Form):
"""Dedicated scrolling log window that stays on top and auto-updates"""
def __init__(self, title):
self.Text = title
self.Size = Size(1000, 600) # Increased size for better readability
self.StartPosition = System.Windows.Forms.FormStartPosition.Manual
self.Location = Point(50, 50) # Better starting position
self.TopMost = True
self.FormBorderStyle = FormBorderStyle.SizableToolWindow
# Create panel for buttons at the top
self.panel = Panel()
self.panel.Dock = DockStyle.Top
self.panel.Height = 40
self.Controls.Add(self.panel)
# Create clear button
self.clear_btn = Button()
self.clear_btn.Text = "Clear Log"
self.clear_btn.Size = Size(100, 30)
self.clear_btn.Location = Point(10, 5)
self.clear_btn.Click += self.clear_log
self.panel.Controls.Add(self.clear_btn)
# Create close button
self.close_btn = Button()
self.close_btn.Text = "Close"
self.close_btn.Size = Size(100, 30)
self.close_btn.Location = Point(120, 5)
self.close_btn.Click += self.close_window
self.panel.Controls.Add(self.close_btn)
# Create open file button
self.file_btn = Button()
self.file_btn.Text = "Open File"
self.file_btn.Size = Size(100, 30)
self.file_btn.Location = Point(230, 5)
self.file_btn.Click += self.open_log_file
self.panel.Controls.Add(self.file_btn)
# Create text box below the button panel
self.log_box = TextBox()
self.log_box.Multiline = True
self.log_box.ScrollBars = ScrollBars.Both # Enable both scrollbars
self.log_box.Dock = DockStyle.Fill
self.log_box.ReadOnly = True
self.log_box.BackColor = Color.FromArgb(30, 30, 30)
self.log_box.ForeColor = Color.LightGray
self.log_box.Font = Font("Consolas", 10) # Monospaced font for better alignment
self.log_box.BorderStyle = BorderStyle.Fixed3D
self.log_box.WordWrap = False # Disable word wrapping to prevent messy formatting
self.Controls.Add(self.log_box)
# Bring text box to front to ensure it's visible
self.log_box.BringToFront()
self.log_path = None
def append_log(self, message):
"""Append message to log window and scroll to end"""
# Convert to Windows-style newlines
message = message.replace("\n", "\r\n")
self.log_box.AppendText(message + "\r\n")
self.log_box.SelectionStart = self.log_box.TextLength
self.log_box.ScrollToCaret()
def clear_log(self, sender, args):
"""Clear the log window"""
self.log_box.Clear()
def close_window(self, sender, args):
"""Close the log window"""
self.Close()
def open_log_file(self, sender, args):
"""Open the log file in default editor"""
if self.log_path and os.path.exists(self.log_path):
subprocess.Popen(['notepad.exe', self.log_path])
class NotificationLogger:
"""
A singleton class to manage logging Revit notifications and command outcomes to a file and window.
Ensures only one instance of the logger exists throughout the script's execution.
"""
_instance = None
log_file_path = None
processed_failures_cache = set()
last_command_outcome = {}
encountered_guids = set()
log_window = None
def __init__(self):
if NotificationLogger._instance is not None:
raise Exception("This class is a singleton! Use get_logger() method.")
# Define and create the log directory in the user's home folder
log_dir = os.path.join(os.path.expanduser('~'), "RevitLogs")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# Generate a timestamp for the log file name
timestamp = System.DateTime.Now.ToString("yyyyMMdd_HHmmss")
self.log_file_path = os.path.join(log_dir, f"RevitNotifications_{timestamp}.txt")
# Write initial header messages to the log file
self.log("=" * 80)
self.log("REVIT NOTIFICATION AND COMMAND LOGGER")
self.log(f"Started at {System.DateTime.Now.ToString('HH:mm:ss')}")
self.log(f"Tracking {len(DELETE_INSTANCE_GUIDS)} GUIDs for auto-deletion")
self.log("=" * 80)
self.log("\nPredefined GUIDs:")
for guid in DELETE_INSTANCE_GUIDS:
self.log(f" {guid}")
self.log("-" * 80)
self.log("Ready to capture Revit notifications and command outcomes.\n")
# Create and show log window
self.create_log_window()
def create_log_window(self):
"""Create the dedicated log window"""
try:
self.log_window = LogWindow("Revit Log Viewer")
self.log_window.log_path = self.log_file_path
self.log_window.Show()
# Add a slight delay to ensure window is fully initialized before logging
System.Threading.Thread.Sleep(100)
self.log("Created dedicated log window")
except Exception as e:
self.log(f"Could not create log window: {str(e)}")
# Fallback to Notepad
self.open_log()
def open_log(self):
"""Open log in Notepad as fallback"""
try:
# Create file if it doesn't exist
if not os.path.exists(self.log_file_path):
with open(self.log_file_path, 'w') as f:
f.write("Revit Notification Log\n")
# Open log in Notepad
subprocess.Popen(['notepad.exe', self.log_file_path])
self.log("Opened log in Notepad")
except Exception as e:
print(f"Could not open log: {str(e)}")
@staticmethod
def get_logger():
if NotificationLogger._instance is None:
NotificationLogger._instance = NotificationLogger()
return NotificationLogger._instance
def log(self, message):
try:
# Write to file
with open(self.log_file_path, 'a') as f:
f.write(f"{message}\n")
# Write to window if available
if self.log_window and not self.log_window.IsDisposed:
self.log_window.append_log(message)
except Exception as e:
print(f"Logging error: {str(e)} - Message: {message}")
def clear_failure_cache(self):
self.processed_failures_cache.clear()
# Global references to prevent garbage collection
_failure_handler = None
_dialog_box_handler = None
_document_changed_handler = None
_application_closing_handler = None
def on_failures_processing(sender, args):
logger = NotificationLogger.get_logger()
timestamp = System.DateTime.Now.ToString("HH:mm:ss")
failures_accessor = args.GetFailuresAccessor()
current_failure_messages = failures_accessor.GetFailureMessages()
if not current_failure_messages or current_failure_messages.Count == 0:
args.SetProcessingResult(FailureProcessingResult.Continue)
return
logger.log(f"\n[FAILURES] {timestamp}")
logger.log("-" * 60)
force_silent_rollback = False
resolved_all = True
for failure in current_failure_messages:
description = failure.GetDescriptionText()
severity = failure.GetSeverity()
failure_id = failure.GetFailureDefinitionId()
guid = failure_id.Guid
logger.encountered_guids.add(guid)
current_failure_key = f"{description}-{severity}-{guid}"
if current_failure_key in logger.processed_failures_cache:
continue
logger.processed_failures_cache.add(current_failure_key)
elements = []
element_ids = []
failing_ids = failure.GetFailingElementIds()
if failing_ids and failing_ids.Count > 0:
current_doc = uiapp.ActiveUIDocument.Document
for id_val in failing_ids:
elem = current_doc.GetElement(id_val)
if elem:
try:
elem_name = getattr(elem, 'Name', '')
if not elem_name:
elem_name = f"Element {id_val.IntegerValue}"
elements.append(elem_name)
element_ids.append(id_val)
except Exception:
elements.append(f"Element {id_val.IntegerValue}")
element_ids.append(id_val)
# Format element list to prevent long lines
element_list = ""
if elements:
element_list = ", ".join(elements) # Show all elements for deletion context
else:
element_list = "No specific elements"
# Log details in a clean, structured format
logger.log(f"GUID: {guid}")
logger.log(f"Type: {severity}")
logger.log(f"Description: {description}")
logger.log(f"Elements: {element_list}")
if guid in DELETE_INSTANCE_GUIDS:
logger.log("GUID Status: IN SUPPRESSION LIST")
else:
logger.log("GUID Status: NOT IN SUPPRESSION LIST")
try:
# =============================================================
# HANDLE DELETE INSTANCE FAILURES
# =============================================================
if guid in DELETE_INSTANCE_GUIDS:
# Attempt resolution with DeleteElements if available
if failure.HasResolutionOfType(FailureResolutionType.DeleteElements):
failing_ids = failure.GetFailingElementIds()
if failures_accessor.IsElementsDeletionPermitted(failing_ids):
resolution_type = FailureResolutionType.DeleteElements
failure.SetCurrentResolutionType(resolution_type)
failures_accessor.ResolveFailure(failure)
# ENHANCED: Log specific deleted elements
del_list = ", ".join(elements) if elements else "No specific elements"
logger.log(f"Resolution: DELETED ELEMENTS: {del_list}")
continue
# Try Default resolution as fallback
if failure.HasResolutionOfType(FailureResolutionType.Default):
resolution_type = FailureResolutionType.Default
failure.SetCurrentResolutionType(resolution_type)
failures_accessor.ResolveFailure(failure)
logger.log(f"Resolution: Applied default action using '{resolution_type}'")
continue
# If we get here, no resolution was available
logger.log("Resolution: No resolution available")
resolved_all = False
force_silent_rollback = True
# =============================================================
# HANDLE WARNINGS (ALWAYS DELETE)
# =============================================================
elif severity == FailureSeverity.Warning:
failures_accessor.DeleteWarning(failure)
logger.log("Resolution: Warning deleted")
# =============================================================
# HANDLE ERRORS (GENERIC)
# =============================================================
elif severity == FailureSeverity.Error:
# Try to resolve with DeleteElements if available
if failure.HasResolutionOfType(FailureResolutionType.DeleteElements):
failing_ids = failure.GetFailingElementIds()
if failures_accessor.IsElementsDeletionPermitted(failing_ids):
resolution_type = FailureResolutionType.DeleteElements
failure.SetCurrentResolutionType(resolution_type)
failures_accessor.ResolveFailure(failure)
# ENHANCED: Log specific deleted elements
del_list = ", ".join(elements) if elements else "No specific elements"
logger.log(f"Resolution: DELETED ELEMENTS: {del_list}")
continue
# Try UnjoinElements resolution
if failure.HasResolutionOfType(FailureResolutionType.UnjoinElements):
resolution_type = FailureResolutionType.UnjoinElements
failure.SetCurrentResolutionType(resolution_type)
failures_accessor.ResolveFailure(failure)
logger.log(f"Resolution: Unjoined elements using '{resolution_type}'")
continue
# Try DetachElements resolution
if failure.HasResolutionOfType(FailureResolutionType.DetachElements):
resolution_type = FailureResolutionType.DetachElements
failure.SetCurrentResolutionType(resolution_type)
failures_accessor.ResolveFailure(failure)
logger.log(f"Resolution: Detached elements using '{resolution_type}'")
continue
# Try Default resolution as last resort
if failure.HasResolutionOfType(FailureResolutionType.Default):
resolution_type = FailureResolutionType.Default
failure.SetCurrentResolutionType(resolution_type)
failures_accessor.ResolveFailure(failure)
logger.log(f"Resolution: Applied default action using '{resolution_type}'")
continue
# If no resolution worked, mark for rollback
logger.log("Resolution: No resolution available")
resolved_all = False
force_silent_rollback = True
# =============================================================
# HANDLE FATAL ERRORS (FORCE ROLLBACK)
# =============================================================
elif severity == FailureSeverity.FatalError:
logger.log("Resolution: Fatal error - cannot resolve")
resolved_all = False
force_silent_rollback = True
except Exception as ex:
logger.log(f"Resolution Error: {str(ex)}")
resolved_all = False
force_silent_rollback = True
logger.log("-" * 40)
if force_silent_rollback or not resolved_all:
failures_accessor.SetClearAfterRollback(True)
args.SetProcessingResult(FailureProcessingResult.ProceedWithRollBack)
logger.log("[RESULT] Dialog suppressed with rollback")
logger.last_command_outcome['status'] = 'Canceled'
logger.last_command_outcome['timestamp'] = timestamp
else:
args.SetProcessingResult(FailureProcessingResult.ProceedWithCommit)
logger.log("[RESULT] All resolved - committed")
logger.last_command_outcome['status'] = 'Completed'
logger.last_command_outcome['timestamp'] = timestamp
logger.clear_failure_cache()
logger.log("=" * 60)
def on_dialog_box_showing(sender, args):
logger = NotificationLogger.get_logger()
timestamp = System.DateTime.Now.ToString("HH:mm:ss")
dialog_id = args.DialogId
dialog_message = args.Message
logger.log(f"\n[DIALOG] {timestamp}")
logger.log(f"ID: {dialog_id}")
logger.log(f"Message: {dialog_message}")
# RESTORED ORIGINAL ID-BASED METHOD: Save prompt detection
# Check if this is a save prompt dialog by ID
if dialog_id in save_prompt_ids:
logger.log("Action: Allowed (save prompt) - NOT suppressing")
return # Don't override this dialog
try:
# For all other dialogs, attempt to dismiss automatically
args.OverrideResult(1) # Usually corresponds to OK/default action
logger.log("Action: Dismissed programmatically")
except Exception as e:
logger.log(f"Action Error: {str(e)}")
def on_document_changed(sender, args):
logger = NotificationLogger.get_logger()
timestamp = System.DateTime.Now.ToString("HH:mm:ss")
logger.log(f"\n[DOC CHANGE] {timestamp}")
logger.log(f"Operation: {args.GetOperation()}")
if logger.last_command_outcome:
status = logger.last_command_outcome.get('status', 'Unknown')
logger.log(f"Correlated with last command: {status}")
logger.last_command_outcome.clear()
def application_closing(sender, args):
logger = NotificationLogger.get_logger()
try:
logger.log("\n" + "=" * 80)
logger.log("SESSION SUMMARY")
logger.log(f"Ended at {System.DateTime.Now.ToString('HH:mm:ss')}")
logger.log(f"\nEncountered {len(logger.encountered_guids)} unique GUIDs:")
for guid in logger.encountered_guids:
status = "IN SUPPRESSION LIST" if guid in DELETE_INSTANCE_GUIDS else "NEW - CONSIDER ADDING"
logger.log(f" {guid} - {status}")
logger.log("\nPredefined GUIDs:")
for guid in DELETE_INSTANCE_GUIDS:
logger.log(f" {guid}")
logger.log("=" * 80)
# Unsubscribe from all events
if _failure_handler:
app.FailuresProcessing -= _failure_handler
if _dialog_box_handler:
uiapp.DialogBoxShowing -= _dialog_box_handler
if _document_changed_handler:
app.DocumentChanged -= _document_changed_handler
logger.log("Event handlers unsubscribed")
except Exception as e:
logger.log(f"Shutdown error: {str(e)}")
finally:
NotificationLogger._instance = None
# --- Script Execution Start ---
logger = NotificationLogger.get_logger()
_failure_handler = on_failures_processing
_dialog_box_handler = on_dialog_box_showing
_document_changed_handler = on_document_changed
_application_closing_handler = application_closing
app.FailuresProcessing += _failure_handler
uiapp.DialogBoxShowing += _dialog_box_handler
app.DocumentChanged += _document_changed_handler
import System.AppDomain
System.AppDomain.CurrentDomain.ProcessExit += _application_closing_handler
System.AppDomain.CurrentDomain.DomainUnload += _application_closing_handler
logger.log("Event handlers attached successfully")