From 9aaabb328bb3a019b9233ecac8fd1520920de0e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 19:38:04 +0000 Subject: [PATCH 1/6] Implement batch rename flow for selected sheets Agent-Logs-Url: https://github.com/pyrevitlabs/pyRevit/sessions/f44fc21e-ca6b-4130-989e-c907d4b17d84 Co-authored-by: jmcouffin <7872003+jmcouffin@users.noreply.github.com> --- .../bundle.yaml | 2 +- .../script.py | 323 ++++++++++++++++-- 2 files changed, 301 insertions(+), 24 deletions(-) diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml index a4f751740b..16c67833a5 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml @@ -7,7 +7,7 @@ title: de_de: Gewählte Pläne umbenennen (klein oder GROSS-Buchstaben) pt_br: Renomear Folhas Selecionadas tooltip: - en_us: Change the selected sheet names + en_us: Batch rename selected sheet names (find/replace, prefix/suffix, or case conversion) fr_fr: Modifier les noms des feuilles sélectionnés ru: Изменить имена выбранных листов. chinese_s: 更改选定图纸的名称 diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py index f8b3144445..244700de93 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py @@ -1,33 +1,310 @@ -"""Change the selected sheet names.""" +"""Rename selected sheet names in batch.""" from pyrevit import revit, DB from pyrevit import forms -def change_case(sheetlist, upper=True, verbose=False): - with revit.Transaction('Rename Sheets to Upper'): - for el in sheetlist: - sheetnameparam = el.Parameter[DB.BuiltInParameter.SHEET_NAME] - orig_name = sheetnameparam.AsString() - new_name = orig_name.upper() if upper else orig_name.lower() - if verbose: - print('RENAMING:\t{0}\n' - ' to:\t{1}\n'.format(orig_name, new_name)) - sheetnameparam.Set(new_name) +RENAME_MODES = [ + 'Find & Replace', + 'Add Prefix', + 'Add Suffix', + 'to UPPERCASE', + 'to lowercase', +] -sel_sheets = forms.select_sheets(title='Select Sheets', use_selection=True) +def _get_sheet_name(sheet): + return sheet.Parameter[DB.BuiltInParameter.SHEET_NAME].AsString() -if sel_sheets: - selected_option, switches = \ - forms.CommandSwitchWindow.show( - ['to UPPERCASE', - 'to lowercase'], - switches=['Show Report'], - message='Select rename option:' + +def _set_sheet_name(sheet, name): + sheet.Parameter[DB.BuiltInParameter.SHEET_NAME].Set(name) + + +def _collect_rename_inputs(mode): + if mode == 'Find & Replace': + find_txt = forms.ask_for_string( + prompt='Find text in current sheet names:', + title='Rename Selected Sheets' ) + if find_txt is None: + return None + if find_txt == '': + forms.alert('Find text can not be empty.') + return None + replace_txt = forms.ask_for_string( + default='', + prompt='Replace with:', + title='Rename Selected Sheets' + ) + if replace_txt is None: + return None + return {'find': find_txt, 'replace': replace_txt} + + if mode == 'Add Prefix': + prefix_txt = forms.ask_for_string( + default='', + prompt='Prefix to add to selected sheet names:', + title='Rename Selected Sheets' + ) + if prefix_txt is None: + return None + if prefix_txt == '': + forms.alert('Prefix can not be empty.') + return None + return {'prefix': prefix_txt} + + if mode == 'Add Suffix': + suffix_txt = forms.ask_for_string( + default='', + prompt='Suffix to add to selected sheet names:', + title='Rename Selected Sheets' + ) + if suffix_txt is None: + return None + if suffix_txt == '': + forms.alert('Suffix can not be empty.') + return None + return {'suffix': suffix_txt} + + return {} + + +def _build_new_name(old_name, mode, data): + if mode == 'Find & Replace': + return old_name.replace(data['find'], data['replace']) + if mode == 'Add Prefix': + return '{}{}'.format(data['prefix'], old_name) + if mode == 'Add Suffix': + return '{}{}'.format(old_name, data['suffix']) + if mode == 'to UPPERCASE': + return old_name.upper() + if mode == 'to lowercase': + return old_name.lower() + return old_name + + +def _print_preview(rename_pairs): + print('Rename preview:') + for idx, pair in enumerate(rename_pairs): + if idx >= 25: + print('... and {} more'.format(len(rename_pairs) - 25)) + break + print('[{}] {} -> {}'.format( + pair['sheet'].SheetNumber, + pair['old_name'], + pair['new_name'] + )) + + +def _report_results(renamed, unchanged, conflicts, failed): + print('Rename Selected Sheets results') + print(' Renamed: {}'.format(len(renamed))) + print(' Unchanged: {}'.format(len(unchanged))) + print(' Conflicts skipped: {}'.format(len(conflicts))) + print(' Failed: {}'.format(len(failed))) + if renamed: + print('\nRenamed sheets:') + for item in renamed: + print(' [{}] {} -> {}'.format( + item['sheet_number'], + item['old_name'], + item['new_name'] + )) + if conflicts: + print('\nSkipped because target name was not unique:') + for item in conflicts: + print(' [{}] {} -> {} ({})'.format( + item['sheet_number'], + item['old_name'], + item['new_name'], + item['reason'] + )) + if failed: + print('\nFailed to rename:') + for item in failed: + print(' [{}] {} -> {} ({})'.format( + item['sheet_number'], + item['old_name'], + item['new_name'], + item['reason'] + )) + + +def _execute_renames(rename_pairs): + renamed = [] + failed = [] + temp_name_map = {} + + with revit.Transaction('Rename Sheets (Temporary Names)'): + for idx, pair in enumerate(rename_pairs): + sheet = pair['sheet'] + temp_name = '__pyrevit_temp_sheet_name_{}_{}__'.format( + sheet.Id.IntegerValue, + idx + ) + try: + _set_sheet_name(sheet, temp_name) + temp_name_map[sheet.Id.IntegerValue] = temp_name + except Exception as err: + failed.append({ + 'sheet_number': pair['sheet'].SheetNumber, + 'old_name': pair['old_name'], + 'new_name': pair['new_name'], + 'reason': 'Could not assign temporary name: {}'.format(err), + }) + + with revit.Transaction('Rename Sheets'): + for pair in rename_pairs: + sheet = pair['sheet'] + if sheet.Id.IntegerValue not in temp_name_map: + continue + try: + _set_sheet_name(sheet, pair['new_name']) + renamed.append({ + 'sheet_number': pair['sheet'].SheetNumber, + 'old_name': pair['old_name'], + 'new_name': pair['new_name'], + }) + except Exception as err: + try: + _set_sheet_name(sheet, pair['old_name']) + except Exception: + pass + failed.append({ + 'sheet_number': pair['sheet'].SheetNumber, + 'old_name': pair['old_name'], + 'new_name': pair['new_name'], + 'reason': 'Could not assign target name: {}'.format(err), + }) + + return renamed, failed + + +def main(): + selected_sheets = forms.select_sheets(title='Select Sheets', use_selection=True) + if not selected_sheets: + return + + selected_option, switches = forms.CommandSwitchWindow.show( + RENAME_MODES, + switches=['Show Report'], + message='Select rename option:' + ) + if not selected_option: + return + + rename_inputs = _collect_rename_inputs(selected_option) + if rename_inputs is None: + return + + all_sheet_names = {} + all_sheets = DB.FilteredElementCollector(revit.doc) \ + .OfClass(DB.ViewSheet) \ + .WhereElementIsNotElementType() \ + .ToElements() + for sheet in all_sheets: + all_sheet_names[sheet.Id.IntegerValue] = _get_sheet_name(sheet) + + rename_pairs = [] + unchanged = [] + for sheet in selected_sheets: + old_name = _get_sheet_name(sheet) + new_name = _build_new_name(old_name, selected_option, rename_inputs) + if old_name == new_name: + unchanged.append({ + 'sheet_number': sheet.SheetNumber, + 'old_name': old_name, + 'new_name': new_name, + }) + continue + rename_pairs.append({ + 'sheet': sheet, + 'old_name': old_name, + 'new_name': new_name, + }) + + if not rename_pairs: + forms.alert('No sheet names changed with selected option.') + if switches['Show Report']: + _report_results([], unchanged, [], []) + return + + selected_sheet_ids = set([x['sheet'].Id.IntegerValue for x in rename_pairs]) + + conflicts = [] + valid_pairs = [] + target_names = {} + for pair in rename_pairs: + target_name = pair['new_name'] + target_names.setdefault(target_name, []).append(pair) + + for tname, pairs in target_names.items(): + if len(pairs) > 1: + for pair in pairs: + conflicts.append({ + 'sheet_number': pair['sheet'].SheetNumber, + 'old_name': pair['old_name'], + 'new_name': pair['new_name'], + 'reason': 'Target name is repeated in selected sheets', + }) + else: + valid_pairs.append(pairs[0]) + + unique_pairs = [] + for pair in valid_pairs: + name_taken = False + for sheet_id, sheet_name in all_sheet_names.items(): + if sheet_id in selected_sheet_ids: + continue + if sheet_name == pair['new_name']: + name_taken = True + break + if name_taken: + conflicts.append({ + 'sheet_number': pair['sheet'].SheetNumber, + 'old_name': pair['old_name'], + 'new_name': pair['new_name'], + 'reason': 'Target name already exists in model', + }) + else: + unique_pairs.append(pair) + + if not unique_pairs: + forms.alert('No valid unique target names. Nothing renamed.') + _report_results([], unchanged, conflicts, []) + return + + _print_preview(unique_pairs) + proceed = forms.alert( + 'Rename {} selected sheets?\n' + '{} sheets are ready to rename.\n' + '{} sheets will be skipped because of conflicts.\n' + 'See output window for preview.'.format( + len(selected_sheets), + len(unique_pairs), + len(conflicts) + ), + title='Rename Selected Sheets', + yes=True, + no=True, + ok=False + ) + if not proceed: + return + + renamed, failed = _execute_renames(unique_pairs) + + if switches['Show Report'] or conflicts or failed: + _report_results(renamed, unchanged, conflicts, failed) + forms.alert( + 'Done.\nRenamed: {}\nSkipped (unchanged/conflicts): {}\nFailed: {}'.format( + len(renamed), + len(unchanged) + len(conflicts), + len(failed) + ), + title='Rename Selected Sheets' + ) + - if selected_option: - change_case(sel_sheets, - upper=True if selected_option == 'to UPPERCASE' else False, - verbose=switches['Show Report']) +main() From 68b6085e113ab2925a08873f624dbbe716edebc1 Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Thu, 21 May 2026 21:46:00 +0200 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../script.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py index 244700de93..4bf68a7afc 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py @@ -167,15 +167,33 @@ def _execute_renames(rename_pairs): 'new_name': pair['new_name'], }) except Exception as err: + restore_reason = '' try: _set_sheet_name(sheet, pair['old_name']) - except Exception: - pass + restore_reason = ' Restored original name.' + except Exception as restore_err: + temp_name = temp_name_map[sheet.Id.IntegerValue] + try: + _set_sheet_name(sheet, temp_name) + restore_reason = ( + ' Could not restore original name: {}. ' + 'Sheet was kept on temporary name: {}.' + .format(restore_err, temp_name) + ) + except Exception as temp_restore_err: + restore_reason = ( + ' Could not restore original name: {}. ' + 'Could not restore temporary name {} either: {}.' + .format(restore_err, temp_name, temp_restore_err) + ) failed.append({ 'sheet_number': pair['sheet'].SheetNumber, 'old_name': pair['old_name'], 'new_name': pair['new_name'], - 'reason': 'Could not assign target name: {}'.format(err), + 'reason': 'Could not assign target name: {}.{}'.format( + err, + restore_reason + ), }) return renamed, failed From 8017a67dad290507851480dfd5d9abe72ef5ada9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 19:46:30 +0000 Subject: [PATCH 3/6] Fix conflict exclusion set for sheet rename validation Agent-Logs-Url: https://github.com/pyrevitlabs/pyRevit/sessions/ff2babe9-505a-449a-b773-d1b0257326f4 Co-authored-by: jmcouffin <7872003+jmcouffin@users.noreply.github.com> --- .../Rename Selected Sheets.pushbutton/script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py index 4bf68a7afc..c0f73d8b79 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py @@ -248,8 +248,6 @@ def main(): _report_results([], unchanged, [], []) return - selected_sheet_ids = set([x['sheet'].Id.IntegerValue for x in rename_pairs]) - conflicts = [] valid_pairs = [] target_names = {} @@ -269,6 +267,8 @@ def main(): else: valid_pairs.append(pairs[0]) + selected_sheet_ids = set([x['sheet'].Id.IntegerValue for x in valid_pairs]) + unique_pairs = [] for pair in valid_pairs: name_taken = False From 296147a7f57fb38992865ae484194573504dc5ea Mon Sep 17 00:00:00 2001 From: Jean-Marc Couffin Date: Thu, 21 May 2026 21:48:13 +0200 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Rename Selected Sheets.pushbutton/bundle.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml index 16c67833a5..caa4e239bb 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/bundle.yaml @@ -7,7 +7,7 @@ title: de_de: Gewählte Pläne umbenennen (klein oder GROSS-Buchstaben) pt_br: Renomear Folhas Selecionadas tooltip: - en_us: Batch rename selected sheet names (find/replace, prefix/suffix, or case conversion) + en_us: Change selected sheet names fr_fr: Modifier les noms des feuilles sélectionnés ru: Изменить имена выбранных листов. chinese_s: 更改选定图纸的名称 From 2c45e3a695899aebe3cd46b3fe1696f745379f23 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 19:49:45 +0000 Subject: [PATCH 5/6] fix: use "cannot" instead of "can not" in user-facing alerts Agent-Logs-Url: https://github.com/pyrevitlabs/pyRevit/sessions/970b7536-efaa-4399-b7fd-0ef0c280a244 Co-authored-by: jmcouffin <7872003+jmcouffin@users.noreply.github.com> --- .../Rename Selected Sheets.pushbutton/script.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py index c0f73d8b79..d0ba22c234 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py @@ -30,7 +30,7 @@ def _collect_rename_inputs(mode): if find_txt is None: return None if find_txt == '': - forms.alert('Find text can not be empty.') + forms.alert('Find text cannot be empty.') return None replace_txt = forms.ask_for_string( default='', @@ -50,7 +50,7 @@ def _collect_rename_inputs(mode): if prefix_txt is None: return None if prefix_txt == '': - forms.alert('Prefix can not be empty.') + forms.alert('Prefix cannot be empty.') return None return {'prefix': prefix_txt} @@ -63,7 +63,7 @@ def _collect_rename_inputs(mode): if suffix_txt is None: return None if suffix_txt == '': - forms.alert('Suffix can not be empty.') + forms.alert('Suffix cannot be empty.') return None return {'suffix': suffix_txt} From 7f286e76849e72e643c51b117139511620354698 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 19:51:54 +0000 Subject: [PATCH 6/6] refactor: precompute existing names set for O(1) conflict checks Agent-Logs-Url: https://github.com/pyrevitlabs/pyRevit/sessions/a9656f4c-86f7-48af-8594-77e0691baf2c Co-authored-by: jmcouffin <7872003+jmcouffin@users.noreply.github.com> --- .../Rename Selected Sheets.pushbutton/script.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py index d0ba22c234..ccafd42495 100644 --- a/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py +++ b/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py @@ -268,17 +268,14 @@ def main(): valid_pairs.append(pairs[0]) selected_sheet_ids = set([x['sheet'].Id.IntegerValue for x in valid_pairs]) + existing_names = set( + name for sid, name in all_sheet_names.items() + if sid not in selected_sheet_ids + ) unique_pairs = [] for pair in valid_pairs: - name_taken = False - for sheet_id, sheet_name in all_sheet_names.items(): - if sheet_id in selected_sheet_ids: - continue - if sheet_name == pair['new_name']: - name_taken = True - break - if name_taken: + if pair['new_name'] in existing_names: conflicts.append({ 'sheet_number': pair['sheet'].SheetNumber, 'old_name': pair['old_name'],