Multiple buttons with one script file

Hello pyRevit friends :slight_smile:

I have a bunch of buttons that have very similar code and I´m tired of debugging and making changes multiple times.

image

So I started to use the exact same script for every button, the logic of the script is driven by the button name, script.get_bundle_name()

So far so good, but copying the script to multiple folders after every change is also annoying.
So the next step to make this more convenient would be to just call the same code file from every button.

But I don´t know how I can do this, should I put the whole code inside a function and use that as a library?

Thankful for any advice!
Kind regards

code:

# -*- coding: utf-8 -*-

# Native and Third-Party Imports
import os
from datetime import datetime
from os.path import exists
from os import remove

# .NET imports
import clr
import System
clr.AddReference("RevitAPI")
clr.AddReference("RevitServices")
clr.AddReference('RevitAPIUI')

# Revit and RevitServices Imports
from Autodesk.Revit import DB
from Autodesk.Revit.DB import (ElementId, BuiltInCategory, BuiltInParameter, FilteredElementCollector, PrintSetting, ViewSheetSet, PrintManager, ViewSet, Transaction)
from Autodesk.Revit.UI import (TaskDialog, TaskDialogCommonButtons, TaskDialogResult, TaskDialogCommandLinkId, TaskDialogIcon)
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# PyRevit Imports
from pyrevit import forms
from pyrevit import script

import Log

# Variables for Active Document and Application
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application

def create_print_folder():

    path = "C:\\Print"
    try:
        if not exists(path):
            os.mkdir(path)
    except Exception as e:
        TaskDialog.Show('Error', "Failed to create directory: {}".format(e))

def create_temp_print_settings():

    outlist = []
    for size, string in zip(PAPER_SIZE_ELEMENTS,PAPER_SIZE_STRINGS):
    
        tempPrintSettingName = "tempPrintSetting_" + string
        PrintParameters = PRINT_MANAGER.PrintSetup.CurrentPrintSetting.PrintParameters
        
        processing_type = get_processing_type()
    
        PrintParameters.PaperSize = size
        PrintParameters.ZoomType = PrintParameters.ZoomType.Zoom
        PRINT_MANAGER.PrintSetup.CurrentPrintSetting.PrintParameters.Zoom = 100
        #PrintParameters.ZoomType = PrintParameters.ZoomType.FitToPage
        PrintParameters.PageOrientation = PrintParameters.PageOrientation.Landscape
        #PrintParameters.PageOrientation = PrintParameters.PageOrientation.Portrait
        PrintParameters.ViewLinksinBlue = False
        PrintParameters.HideReforWorkPlanes = True
        PrintParameters.HideUnreferencedViewTags = True
        PrintParameters.MaskCoincidentLines = True
        PrintParameters.HideScopeBoxes = True
        PrintParameters.HideCropBoundaries = True
        PrintParameters.ReplaceHalftoneWithThinLines = True
        
        PrintParameters.ColorDepth = PrintParameters.ColorDepth.Color
        #PrintParameters.ColorDepth = PrintParameters.ColorDepth.GrayScale
        #PrintParameters.ColorDepth = PrintParameters.ColorDepth.BlackLine
        
        if processing_type == "Raster":
            PrintParameters.HiddenLineViews = PrintParameters.HiddenLineViews.RasterProcessing
            #print "Raster"
        else:
            PrintParameters.HiddenLineViews = PrintParameters.HiddenLineViews.VectorProcessing
            #print "Vector"
        #PrintParameters.RasterQuality = PrintParameters.RasterQuality.High
        #PrintParameters.RasterQuality = PrintParameters.RasterQuality.Low
        #PrintParameters.RasterQuality = PrintParameters.RasterQuality.Medium
        PrintParameters.RasterQuality = PrintParameters.RasterQuality.Presentation
        
        #PrintParameters.PaperPlacement = PrintParameters.PaperPlacement.Center
        PrintParameters.PaperPlacement = PrintParameters.PaperPlacement.LowerLeft
        PrintParameters.MarginType = PrintParameters.MarginType.NoMargin
        #PrintParameters.MarginType = PrintParameters.MarginType.PrinterLimit
        #PrintParameters.MarginType = PrintParameters.MarginType.UserDefined
    
        PRINT_MANAGER.PrintSetup.SaveAs(tempPrintSettingName)
        PRINT_MANAGER.Apply()
        outlist.append(tempPrintSettingName)
    return outlist
    
def print_view(doc, sheet, pRange, printerName, combined, filePath, printSetting):

    # Create a set of views and insert the given sheet
    view_set = ViewSet()
    view_set.Insert(sheet)

    # Make the newly created view set the current one
    view_sheet_setting = PRINT_MANAGER.ViewSheetSetting
    view_sheet_setting.CurrentViewSheetSet.Views = view_set

    # Configure print manager settings for virtual printers
    if PRINT_MANAGER.IsVirtual:
        PRINT_MANAGER.CombinedFile = combined
        PRINT_MANAGER.PrintToFile = True
        PRINT_MANAGER.PrintToFileName = filePath

    # Delete existing file to avoid override prompts
    if exists(filePath):
        try:
            remove(filePath)
        except:
            user_message("File is open in another application", "Close the PDF before printing")

    # Set and apply print settings
    try:
        print_setup = PRINT_MANAGER.PrintSetup
        print_setup.CurrentPrintSetting = printSetting
    except Exception as e:
        TaskDialog.Show('Error', "Failed to set print settings: {}".format(e))

    # Save settings, submit the print job, and clean up
    view_sheet_setting.SaveAs("tempSetName")
    PRINT_MANAGER.Apply()
    PRINT_MANAGER.SubmitPrint()
    view_sheet_setting.Delete()

def get_active_sheet():

    active_view = doc.ActiveView
    if active_view.Category.Id == ElementId(BuiltInCategory.OST_Sheets):
        return [active_view]
    return []

def create_file_paths():

    outlist=[]
    for sheet in SHEETS:
        sheetname = sheet.Name
        sheetnumber = sheet.SheetNumber
        RevitFileName = doc.Title

        if RevitFileName == "_BIM360_R2022":
            Ausgabe = sheet.LookupParameter("9. AUSGABE DOK.NR").AsString()
            Version = sheet.LookupParameter("9.1 VERSION DOK.NR").AsString()
            filepath = "C:\\Print\\"+sheetnumber+"-"+Ausgabe+"_"+Version+".pdf"
        else:
            filepath = "C:\\Print\\"+sheetnumber+"-"+sheetname+".pdf"
        outlist.append(filepath)
    return outlist

def delete_old_print_settings():
    ids_to_delete = []

    # First, collect the IDs of the settings you want to delete
    try:
        for setting in ALL_PRINT_SETTINGS:
            if "tempPrintSetting_" in setting.Name:
                ids_to_delete.append(setting.Id)
    except Exception as e:
        TaskDialog.Show("Error Collecting Print Settings", "Error collecting temp print settings for deletion: {}".format(e))
        return

    # Now, delete the settings using the collected IDs
    try:
        for setting_id in ids_to_delete:
            doc.Delete(setting_id)
    except Exception as e:
        TaskDialog.Show("Error Deleting Print Settings", "Error deleting temp print settings: {}".format(e))

def set_vorpause():

    # Filter sheets that have the "VORPAUSE" parameter
    sheets_to_update = [sheet for sheet in SHEETS if sheet.LookupParameter("VORPAUSE")]
    
    # If there are no sheets to update, exit
    if not sheets_to_update:
        return

    # Start the transaction
    transaction = Transaction(doc, 'Set Vorpause')
    
    try:
        transaction.Start()
        
        for sheet in sheets_to_update:
            vorpause_para = sheet.LookupParameter("VORPAUSE")
            if vorpause_para:
                if "Vorpause" in BUNDLE_NAME:
                    VorpauseValue = "VORPAUSE " + Date
                    vorpause_para.Set(VorpauseValue)
                else:
                    vorpause_para.Set(" ")

        transaction.Commit()

    except Exception as e:
        # If there's an error, roll back the transaction and show a TaskDialog
        transaction.RollBack()
        TaskDialog.Show("Error", "Failed to set VORPAUSE parameter: {}".format(e))
        
def get_paper_size():

    paper_size_strings  = []
    paper_size_elements =[]
    size_errors=[]
    for sheet in SHEETS:
        first_ttbl = FilteredElementCollector(doc,sheet.Id).OfCategory(BuiltInCategory.OST_TitleBlocks).FirstElement()
        if first_ttbl:
            width = first_ttbl.get_Parameter(BuiltInParameter.SHEET_WIDTH).AsDouble() * 0.3048
            # Compare the width value with tolerance
            if abs(width - 1.19) < 1e-3:
                size = "A0"
            elif abs(width - 0.84) < 1e-3:
                size = "A1"
            elif abs(width - 0.59) < 1e-3:
                size = "A2"
            elif abs(width - 0.42) < 1e-3:
                size = "A3"
            else:
                size = "A0"
                size_errors.append("Size not found. A0 was used instead.")
            paper_size_strings.append(size)
        else:
            size_errors.append("No Title Block found on the Sheet.")
    if paper_size_strings:
        for i in paper_size_strings :
            for x in PRINT_MANAGER.PaperSizes:
                if i == x.Name:
                    paper_size = x
                    paper_size_elements.append(paper_size)
                    break  # exit inner loop once match is found
    return paper_size_elements, paper_size_strings , size_errors

def collect_print_settings():

    outlist = []
    for cps in TEMP_PRINT_SETTINGS:
        for aps in ALL_PRINT_SETTINGS:
            if aps.Name == cps:
                print_setting = aps
                outlist.append(print_setting)
                break
    return outlist
    
def delete_view_sets():

    try:
        view_sets = FilteredElementCollector(doc).OfClass(ViewSheetSet)
        ids_to_delete = [i.Id for i in view_sets if i.Name == "tempSetName"]

        for view_set_id in ids_to_delete:
            doc.Delete(view_set_id)

    except Exception as e:
        TaskDialog.Show('Error', "An error occurred: {}".format(e))

def delete_print_settings():
    ids_to_delete = []

    # First, collect the IDs of the print settings you want to delete
    try:
        for print_setting in PRINT_SETTINGS:
            ids_to_delete.append(print_setting.Id)
    except Exception as e:
        TaskDialog.Show('Error', "An error occurred while collecting print settings for deletion: {}".format(e))
        return

    # Now, delete the print settings using the collected IDs
    try:
        for setting_id in ids_to_delete:
            doc.Delete(setting_id)
    except Exception as e:
        TaskDialog.Show('Error', "An error occurred while deleting print settings: {}".format(e))

def user_message(line1,line2):

    taskDialog = TaskDialog("Results")
    taskDialog.MainInstruction = line1
    taskDialog.MainContent = line2
    taskDialog.TitleAutoPrefix = False  
    taskDialog.MainIcon = TaskDialogIcon.TaskDialogIconInformation  
    taskDialog.CommonButtons = TaskDialogCommonButtons.Close #| TaskDialogCommonButtons.Yes 
    #taskDialog.FooterText = "Help"
    return taskDialog.Show()

def get_processing_type():

    try:
        pdf_config = script.get_config("PDFsetting")
        processing_option = pdf_config.get_option('hidden_lines_processing')
        return processing_option
    except Exception as e:
        TaskDialog.Show('Error', "An error occurred while fetching the processing type: {}".format(e))
        return None

def get_sheet_set():

    active_sheet = None
    
    # Check if the ActiveView's category is 'OST_Sheets'.
    if ACTIVE_VIEW.Category.Id == ElementId(BuiltInCategory.OST_Sheets):
        active_sheet = ACTIVE_VIEW
    
    # If an Active Sheet is found, get its parameters.
    if active_sheet:
        para = active_sheet.GetParameters(BrowserSortParameter)
        if para:
            # Assuming there's only one parameter value we care about.
            paravalue = para[0].AsValueString()
        else:
            paravalue = None

    # If we found a parameter value, use it to filter sheets.
    if paravalue:
        provider = ParameterValueProvider(para[0].Id)
        evaluator = FilterStringContains()
        rule = FilterStringRule(provider, evaluator, paravalue)
        filter = ElementParameterFilter(rule)
        
        sheet_set = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Sheets).WherePasses(filter).ToElements()
    else:
        sheet_set = []

    return sheet_set
    
def get_open_sheets():
    outlist = []
    uiviews = uidoc.GetOpenUIViews()
    for uiview in uiviews:
        view = doc.GetElement(uiview.ViewId)
        if view.Category.Name == "Sheets" and view.Name != "None":
            outlist.append(view)
    sorted_elements = sorted(outlist, key=lambda x: x.SheetNumber)
    return sorted_elements

def get_sheet_number(sheet):
    return sheet.get_Parameter(BuiltInParameter.SHEET_NUMBER).AsString()

def get_all_sheets():
    sheets = FilteredElementCollector(doc).OfClass(ViewSheet).ToElements()
    sorted_sheets = sorted(sheets, key=get_sheet_number)
    return sorted_sheets

def get_sheet_set_names():
    outlist = []
    if BrowserSortParameter:
        for s in ALL_SHEETS:
            paravalue = s.LookupParameter(BrowserSortParameter).AsString()
            if paravalue not in outlist and paravalue != None:
                outlist.append(paravalue)
    return outlist

def get_sheets_by_sheet_set():
    outlist = []
    for name in SHEET_SET_NAMES:
        outlist2 = []
        for sheet in ALL_SHEETS:
            paravalue = sheet.LookupParameter(BrowserSortParameter).AsString()
            if paravalue == name:
                outlist2.append(sheet)
        outlist.append(outlist2)
    return outlist
    
def add_open_sheets():
    if OPEN_SHEETS:
        SHEET_SET_NAMES.insert(0,"0_Open")
        SHEETS_BY_SHEET_SET.insert(0,OPEN_SHEETS)
    return SHEET_SET_NAMES, SHEETS_BY_SHEET_SET
    

class MyOptionSheet(forms.TemplateListItem):
    @property
    def name(self):
        return '{} - {}'.format(
            self.item.SheetNumber,
            self.item.Name)

def natural_sort_key(key):
    return [int(c) if c.isdigit() else c.lower() for c in re.split('(\d+)', key)]

def select_sheets():
    ops = {}
    AllSheets = []
    AllSheets.extend([MyOptionSheet(x) for x in ALL_SHEETS if not x.IsPlaceholder])
    ops['1_All'] = AllSheets

    for name, sheets in zip(SHEET_SET_NAMES,SHEETS_BY_SHEET_SET):
        sheets = []
        for sheet in sheets:
            sheets.append(MyOptionSheet(sheet))
        ops[name] = sheets

    sorted_ops = OrderedDict(sorted(ops.items(), key=lambda x: natural_sort_key(x[0])))

    if OPEN_SHEETS:
        selection = forms.SelectFromList.show(sorted_ops, 'Print Sheets PDF', group_selector_title='Sheet Sets', default_group= '0_Open', multiselect=True)
    else:
        selection = forms.SelectFromList.show(sorted_ops, 'Print Sheets PDF', group_selector_title='Sheet Sets', default_group= '1_All', multiselect=True)

    return selection

# Get bundle Name
BUNDLE_NAME = script.get_bundle_name()

# Get the current date
now = datetime.now()
DATE_FORMAT = "%d.%m.%Y"
date = now.strftime(DATE_FORMAT)

# Initial setup
PRINTER_NAME = "PDF-XChange Standard"
create_print_folder()
FILE_PATHS = create_file_paths()
SIZE_ERRORS = None

ACTIVE_VIEW = doc.ActiveView

if "Active" in BUNDLE_NAME:
    SHEETS = get_active_sheet()
    
elif "Set" in BUNDLE_NAME:
    SHEETS = get_sheet_set()
    
elif "Select" in BUNDLE_NAME:
    OPEN_SHEETS = get_open_sheets()
    ALL_SHEETS = get_all_sheets()
    SheetSetNames = get_sheet_set_names()
    SHEETS_BY_SHEET_SET = get_sheets_by_sheet_set()
    SHEETS = select_sheets()

SHEET_SET_NAMES, SHEETS_BY_SHEET_SET = add_open_sheets()

if SHEETS and FILE_PATHS:

    ALL_PRINT_SETTINGS = FilteredElementCollector(doc).OfClass(PrintSetting)

    set_vorpause()

    t = Transaction(doc, 'Print PDF')
    t.Start()

    delete_view_sets()
    delete_old_print_settings()
    
    PRINT_MANAGER = doc.PrintManager 
    PRINT_MANAGER.SelectNewPrintDriver(PRINTER_NAME)
    p_range = System.Enum.Parse(DB.PrintRange, "Select")
    combined = True
    PRINT_MANAGER.PrintRange = p_range
    PRINT_MANAGER.Apply()
    
    PAPER_SIZE_ELEMENTS, PAPER_SIZE_STRINGS, SIZE_ERRORS = get_paper_size()
    TEMP_PRINT_SETTINGS = create_temp_print_settings()
    PRINT_SETTINGS = collect_print_settings()

    if PRINT_SETTINGS:
        try:
            for sheet, print_setting, file_path in zip(SHEETS, PRINT_SETTINGS, FILE_PATHS):
                print_view(doc, sheet, p_range, PRINTER_NAME, combined, file_path, print_setting)
        except Exception as e:
            t.RollBack()
            user_message("Error", "An error occurred during printing: {}".format(e))
        else:
            delete_print_settings()
            t.Commit()
            os.startfile("C:\Print")
    else:
        t.RollBack()
        user_message("Error", "An error occurred when creating the print settings")

if not SHEETS:
    user_message("No active Sheet", "A Sheet has to be active before running")

if SIZE_ERRORS:
    user_message("Error at getting the Paper Size", SIZE_ERRORS[0])

Log.LOG_Entry()
Log.Read_LOG()
Log.Write_LOG()

1 Like

I put my whole code in a definition and put it into a library.

In my pushbuttons I have only the following code:

# -*- coding: utf-8 -*-

# .NET imports
import clr
clr.AddReference("RevitAPI")
clr.AddReference("RevitServices")
clr.AddReference('RevitAPIUI')

# pyRevit imports
from pyrevit import script

# Custom Library Imports
import Print

# Variables for Active Document and Application
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document

# Get bundle Name
BUNDLE_NAME = script.get_bundle_name()

Print.print_pdf(BUNDLE_NAME,doc,uidoc)

It works great :slight_smile: is this method common practice or is there a better way?

3 Likes

This is what I have done before. I had a job with a relatively large code base and I started coding almost completely in a library, with the buttons being an import of the code and letting the library import Autodesk stuff. A button would look something like this:

from company_name.tools import coloring

coloring.color_by_service()

I could then use that same code when I’m doing something else and as a benefit to the user, run this script that would be a button by itself.

1 Like