More than 2 years ago I made an article to show a way to rotate element using Revit API. Using external events in a modeless form as described in my previous article you can for example make a GUI to get axis and angle from user inputs. It is also using ISelectionFilter as described in this previous article. The new thing is that I use a standard class to store rotation parameters. This way parameters are dynamically feed to methods which are run in external events.
Let’s see this in action :
Full source code with comments (designed to be used in pyRevit) :
from revitutils import doc, uidoc from scriptutils.userinput import WPFWindow # noinspection PyUnresolvedReferences from Autodesk.Revit.DB import Transaction, ElementTransformUtils, Line, XYZ, Location, UnitType, UnitUtils # noinspection PyUnresolvedReferences from Autodesk.Revit.UI.Selection import ObjectType, ISelectionFilter # noinspection PyUnresolvedReferences from Autodesk.Revit.UI import IExternalEventHandler, IExternalApplication, Result, ExternalEvent, IExternalCommand # noinspection PyUnresolvedReferences from Autodesk.Revit.Exceptions import InvalidOperationException, OperationCanceledException __doc__ = "Rotate object in any direction" __title__ = "3D Rotate" __author__ = "Cyril Waechter" # Get current project units for angles angle_unit = doc.GetUnits().GetFormatOptions(UnitType.UT_Angle).DisplayUnits def xyz_axis(element_id): """Input : Element, Output : xyz axis of the element""" origin = doc.GetElement(element_id).Location.Point xyz_direction = [XYZ(origin.X + 1, origin.Y, origin.Z), XYZ(origin.X, origin.Y + 1, origin.Z), XYZ(origin.X, origin.Y, origin.Z + 1)] axis = [] for direction in xyz_direction: axis.append(Line.CreateBound(origin, direction)) return axis class AxisISelectionFilter(ISelectionFilter): """ISelectionFilter that allow only which have an axis (Line)""" # noinspection PyMethodMayBeStatic, PyPep8Naming def AllowElement(self, element): if isinstance(element.Location.Curve, Line): return True else: return False def axis_selection(): """Ask user to select an element, return the axis of the element""" try: reference = uidoc.Selection.PickObject(ObjectType.Element, AxisISelectionFilter(), "Select an axis") except OperationCanceledException: pass else: axis = doc.GetElement(reference).Location.Curve return axis class RotateElement(object): """class used to store rotation parameters. Methods then rotate elements.""" def __init__(self): self.selection = uidoc.Selection.GetElementIds() self.angles = [0] def around_itself(self): """Method used to rotate elements on themselves""" try: t = Transaction(doc, "Rotate around itself") t.Start() for elid in self.selection: el_axis = xyz_axis(elid) for i in range(3): if self.angles[i] == 0: pass else: ElementTransformUtils.RotateElement(doc, elid, el_axis[i], self.angles[i]) t.Commit() except InvalidOperationException: import traceback traceback.print_exc() except: import traceback traceback.print_exc() def around_axis(self): """Method used to rotate elements around selected axis""" try: axis = axis_selection() t = Transaction(doc, "Rotate around axis") t.Start() ElementTransformUtils.RotateElements(doc, self.selection, axis, self.angles) t.Commit() except InvalidOperationException: import traceback traceback.print_exc() except: import traceback traceback.print_exc() finally: uidoc.Selection.SetElementIds(rotate_elements.selection) rotate_elements = RotateElement() # Create a subclass of IExternalEventHandler class RotateElementHandler(IExternalEventHandler): """Input : function or method. Execute input in a IExternalEventHandler""" # __init__ is used to make function from outside of the class to be executed by the handler. \ # Instructions could be simply written under Execute method only def __init__(self, do_this): self.do_this = do_this # Execute method run in Revit API environment. # noinspection PyPep8Naming, PyUnusedLocal def Execute(self, application): try: self.do_this() except InvalidOperationException: # If you don't catch this exeption Revit may crash. print "InvalidOperationException catched" # noinspection PyMethodMayBeStatic, PyPep8Naming def GetName(self): return "Execute an function or method in a IExternalHandler" # Create handler instances. Same class (2 instance) is used to call 2 different method. around_itself_handler = RotateElementHandler(rotate_elements.around_itself) around_axis_handler = RotateElementHandler(rotate_elements.around_axis) # Create ExternalEvent instance which pass these handlers around_itself_event = ExternalEvent.Create(around_itself_handler) around_axis_event = ExternalEvent.Create(around_axis_handler) class RotateOptions(WPFWindow): """ Modeless WPF form used for rotation angle input """ def __init__(self, xaml_file_name): WPFWindow.__init__(self, xaml_file_name) self.set_image_source("xyz_img", "XYZ.png") self.set_image_source("plusminus_img", "PlusMinusRotation.png") # noinspection PyUnusedLocal def around_itself_click(self, sender, e): try: rotate_elements.selection = uidoc.Selection.GetElementIds() angles = [self.x_axis.Text, self.y_axis.Text, self.z_axis.Text] for i in range(3): angles[i] = UnitUtils.ConvertToInternalUnits(float(angles[i]), angle_unit) rotate_elements.angles = angles except ValueError: self.warning.Text = "Incorrect angles, input format required '0.0'" else: self.warning.Text = "" around_itself_event.Raise() # noinspection PyUnusedLocal def around_axis_click(self, sender, e): try: rotate_elements.angles = UnitUtils.ConvertToInternalUnits(float(self.rotation_angle.Text), angle_unit) rotate_elements.selection = uidoc.Selection.GetElementIds() except ValueError: self.warning.Text = "Incorrect angles, input format required '0.0'" else: around_axis_event.Raise() RotateOptions('RotateOptions.xaml').Show()
Enjoy !
Please help
I have downloaded api rotate but I still can’t rotate my item. I get an error saying ; can’t rotate element into this position
Some elements like lighting fixtures can’t be rotated in any direction. Please try with a non hosted air terminal for example, any pipe, duct, fitting etc…
It is a Revit limitation the only way to bypass it is to create rotation parameters inside this kind of families.
Hi, Thanks for sharing.
With your python code, I still cannot rote the model in any direction (can only rote in y-axis ).
Could you please show more details about how to break the limitation.
Thanks again.
Hi,
As said above (my last reply). Some objects can rotate only on z-axis. What kind of object are you trying to rotate ? To break this limitation you need to build your family based on a reference line.
Following videos might help :
https://www.youtube.com/watch?v=i97k1zlUq5I
https://www.youtube.com/watch?v=1wdV2znoyG0
Hi Cyril
would you mind tell me which family type are you use in your video please?
most of objects in Revit cannot rotate in more than one axis (vertical with the work plane)
I am trying to build a family model that can rotate in all three axis without using reference line and angle parameters.
I have tried pipe elements but it’s not work when I use your plugin. maybe I use it in a wrong way?
Thank you
fuller
Hi Fuller,
I use this tool commonly for duct/pipe, duct/pipe fittings, duct/pipe accessories. In the video I also rotate an air terminal (it works as long as it is not an hosted family).
You get a message like «you cannot rotate this element in that direction» ? If yes. You need to build a family based on a reference line as mentionned in my previous answers.
Cheers,
Cyril
Hi,
I would like to test your code, but I’m getting an exception.
Exception : IronPython.Runtime.Exceptions.ImportException: No module named revitutils
How do I install the missing module?
Sorry, but I’m really new to Revit.
Thanks Andreas
Hi,
1. Check that you run pyRevit last version which is 4.7.4.
2. Search for a similar issues here : https://github.com/CyrilWaechter/pyRevitMEP/issues
3. If you don’t find a similar issue open a new one providing all necessary informations (Revit version, pyRevit version (+ engine + rocket mode etc… -> about screen), pyRevitMEP last update, error message, step to reproduce your error etc…)