Hello everyone, I’m trying to create a pyRevit script that lets me pick one or more elements, choose a new target Level, and have those elements reassigned to that Level without moving vertically in the model — meaning their absolute world elevation stays the same by automatically adjusting the offset value, is that possible in any sort of way?
Hi @MARK-A,
Welcome,
I would suggest having a look at LevelAutoSet | Revit | Autodesk App Store and not wasting time and effort on this script as this cheap tool does so much in that regard _ unless you have time and the purpose of the tool is mostly to learn how to make it.
Thank you for your reply, I’ll check it out, but yes as you said, it’s also for learning purpose.
Hi @MARK-A
for learning purpose, maybe you can try to look on this tool:
Thank you very much, very useful, I’ll put my code here if I was able to do it.
Here is the script I made with the help of AI, it works great with floors and walls, unfortunately I’m not able to make it work with other structural elements such as stairs and columns and beams. any feedback or tips is appreciated
""" Change element levels while maintaining position Supports Floors & Walls (for now) Author: Mark Alkazzi """ from Autodesk.Revit.DB import * from pyrevit import revit, forms doc = revit.doc uidoc = revit.uidoc def pick_elements(): """Get currently selected elements""" selection_ids = uidoc.Selection.GetElementIds() if not selection_ids: forms.alert("Please select elements first!", exitscript=True) return [doc.GetElement(id) for id in selection_ids] def pick_target_level(): """Present user with level selection dialog""" levels = list(FilteredElementCollector(doc).OfClass(Level)) level_names = [lvl.Name for lvl in levels] # Use the correct SelectFromList.show() method # When multiselect=False, it returns the selected item directly (not in a list) selected_level_name = forms.SelectFromList.show( level_names, title='Select Target Level', button_name='Select Level', multiselect=False ) # Handle the case where user cancels or no selection is made if selected_level_name: # Find the level object that matches the selected name for lvl in levels: if lvl.Name == selected_level_name: return lvl return None def change_element_level(element, target_level): """Change element level while maintaining world position""" try: # Check if element has a LevelId parameter if not hasattr(element, 'LevelId') or not element.LevelId: print("Warning: Element {} does not have a valid LevelId".format(element.Id)) return False current_level = doc.GetElement(element.LevelId) if not current_level: print("Warning: Could not find current level for element {}".format(element.Id)) return False # Skip if already on target level if current_level.Id == target_level.Id: print("Element {} is already on target level".format(element.Id)) return True delta = current_level.Elevation - target_level.Elevation if isinstance(element, Floor): # Change floor level and adjust height above level offset level_param = element.get_Parameter(BuiltInParameter.LEVEL_PARAM) offset_param = element.get_Parameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM) if level_param and offset_param: level_param.Set(target_level.Id) new_offset = offset_param.AsDouble() + delta offset_param.Set(new_offset) print("Successfully changed floor level: Element {}".format(element.Id)) return True elif isinstance(element, Wall): # Change wall base constraint and adjust base offset base_param = element.get_Parameter(BuiltInParameter.WALL_BASE_CONSTRAINT) offset_param = element.get_Parameter(BuiltInParameter.WALL_BASE_OFFSET) if base_param and offset_param: base_param.Set(target_level.Id) new_offset = offset_param.AsDouble() + delta offset_param.Set(new_offset) print("Successfully changed wall level: Element {}".format(element.Id)) return True elif isinstance(element, FamilyInstance): # Handle columns and other family instances if hasattr(element, 'StructuralType') and element.StructuralType.ToString() == "Column": # Structural column level_param = element.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM) offset_param = element.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_OFFSET_PARAM) if level_param and offset_param: level_param.Set(target_level.Id) new_offset = offset_param.AsDouble() + delta offset_param.Set(new_offset) print("Successfully changed column level: Element {}".format(element.Id)) return True else: # General family instance level_param = element.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM) if level_param: # Check if element has free host offset parameter offset_param = element.get_Parameter(BuiltInParameter.INSTANCE_FREE_HOST_OFFSET_PARAM) if offset_param: level_param.Set(target_level.Id) new_offset = offset_param.AsDouble() + delta offset_param.Set(new_offset) print("Successfully changed family instance level: Element {}".format(element.Id)) return True else: # Just change level without offset adjustment level_param.Set(target_level.Id) print("Changed family instance level (no offset): Element {}".format(element.Id)) return True elif element.Category and element.Category.Name == "Stairs": # Basic stairs support - may need adjustment based on stair type level_param = element.get_Parameter(BuiltInParameter.STAIRS_BASE_LEVEL_PARAM) offset_param = element.get_Parameter(BuiltInParameter.STAIRS_BASE_OFFSET) if level_param: level_param.Set(target_level.Id) if offset_param: new_offset = offset_param.AsDouble() + delta offset_param.Set(new_offset) print("Successfully changed stairs level: Element {}".format(element.Id)) return True print("Warning: Element type not fully supported or parameters not accessible: {}".format(element.Category.Name if element.Category else "Unknown")) return False except Exception as e: print("Error processing element {}: {}".format(element.Id, str(e))) return False def main(): """Main script execution""" # Get selected elements elements = pick_elements() # Get target level target_level = pick_target_level() if not target_level: forms.alert("No level selected. Operation cancelled.") return # Process elements within transaction with revit.Transaction("Change Element Levels"): success_count = 0 total_count = len(elements) for element in elements: if change_element_level(element, target_level): success_count += 1 else: print("Failed to process element: {} (ID: {})".format( element.Category.Name if element.Category else "Unknown", element.Id )) print("Successfully processed {}/{} elements".format(success_count, total_count)) if success_count > 0: forms.alert("Successfully changed level for {}/{} elements to '{}'".format( success_count, total_count, target_level.Name)) else: forms.alert("No elements were successfully processed. Check output for details.") # Execute main function if __name__ == '__main__': main()
What error messages are you getting?
for columns : Error processing element 2083213: The parameter is read-only.
for beams :Failed to process element: Structural Framing (ID: 2237700)
Warning: Could not find current level for element 2237714
for stairs: Warning: Could not find current level for element 4395551
Failed to process element: Stairs (ID: 4395551)
this should help Fixes column level and offset in Revit using Dynamo · GitHub
Base Level parameters aren’t the same depending on the object type/category
I suggest you explore the elements you want to host differently using the RevitLookup tool
thanks Jean-marc I’ll check it out when I have more time for it, for now I’m happy with it since it works with the walls and Floors, and the others I’ll do manually