-
-
Notifications
You must be signed in to change notification settings - Fork 451
Extend “Rename Selected Sheets” to support true batch renaming modes #3389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
6
commits into
develop
Choose a base branch
from
copilot/check-if-issue-880-is-still-valid
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
9aaabb3
Implement batch rename flow for selected sheets
Copilot 68b6085
Potential fix for pull request finding
jmcouffin 8017a67
Fix conflict exclusion set for sheet rename validation
Copilot 296147a
Potential fix for pull request finding
jmcouffin 2c45e3a
fix: use "cannot" instead of "can not" in user-facing alerts
Copilot 7f286e7
refactor: precompute existing names set for O(1) conflict checks
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
338 changes: 315 additions & 23 deletions
338
...pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Rename Selected Sheets.pushbutton/script.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,325 @@ | ||
| """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 cannot 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 cannot 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 cannot 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: | ||
| restore_reason = '' | ||
| try: | ||
| _set_sheet_name(sheet, pair['old_name']) | ||
| 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, | ||
| restore_reason | ||
| ), | ||
| }) | ||
|
|
||
| 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 | ||
|
|
||
| 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]) | ||
|
|
||
| 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: | ||
| if pair['new_name'] in existing_names: | ||
| 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() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 2c45e3a — changed all three instances of "can not" to "cannot" (lines 33, 53, 66).