Author Archives: Cyril Waechter

[PyRevitMEP] Shared Parameter Manager

Managing shared parameters is a pain. The standard Revit GUI makes hard to create/manage a lot of parameters. The first version of the shared parameter manager is pretty old but was not as handy to use. Making an easy to use one was a tough job.

In this script is used a WPF DataGrid which nicely displays objects data and is able to auto generate a column for each attribute of an object including drop-down menu (aka combo-box) for enumeration, checkbox for boolean, textbox for sub-objects which can be represented by a string. But for some reason you have to click once to select the row and a second time to edit (check a box, activate drop-down or text edition) which makes it pretty unusable for edition. To solve this you need manually generate all columns with a data template, add handler to make bidirectional communication between displayed data and data in the background really used, sorting etc…

Some subjects are more complex that expected like actually sorting. As often I found answers on stack overflow : https://stackoverflow.com/questions/16956251/sort-a-wpf-datagrid-programmatically

As always source code is available. You’ll find it on github : https://github.com/CyrilWaechter/pyRevitMEP

My first objective with this tool and other related tools is to be able to easily add IFC parameters to objects (families) and project in order to produce better IFC files thereby improving interoperability.

[pyRevitMEP] Transition between 2 elements

Someone told me that the common add-in they were using to make transition between 2 elements was discontinued in Revit 2018. So I made mine I’ve been surprised about how easy and short it is :

with rpw.db.Transaction("Create transition"):
    doc.Create.NewTransitionFitting(connector1, connector2)

So the only thing you need is to prompt user to pick 2 elements (targeting desired connectors) and it is exactly what I described in my previous article.

[pyRevitMEP] ConnectTo : connect MEP elements

Objective :

Even when you master it. Connecting MEP elements in order to get a fully functional system with flow and pressure loss calculation is a pain. So learn Revit API ways to connect things is a good way help you manage this.

Connecting 2 elements and possible funny possible results

Get connector manager

Connector manager is the way to retrieve connectors and all info about it. When you talk about MEP elements there is 2 main kind of elements. MEPCurve (duct, pipe, cable tray, wire etc…) and FamilyInstance (fittings, mechanical equipments, plug, lights etc…) and ConnectorManager is retrieved in 2 different ways :
MEPCurve -> MEPCurve.ConnectorManager
FamilyInstance -> FamilyInstance.MEPModel.ConnectorManager

So it is handy to have a function to retrieve it on any object :

def get_connector_manager(element):
    # type: (Element) -> ConnectorManager
    """Return element connector manager"""
    try:
        # Return ConnectorManager for pipes, ducts etc…
        return element.ConnectorManager
    except AttributeError:
        pass

    try:
        # Return ConnectorManager for family instances etc…
        return element.MEPModel.ConnectorManager
    except AttributeError:
        raise AttributeError("Cannot find connector manager in given element")

Get a connector

As much as I know there is no way to ask user to select a connector. So you need to figure out what connector user want to designate. A good way is to check which connector is the closest (e.g. closest to user selection point or to an other object). A function to check this is then also handy :

def get_connector_closest_to(connectors, xyz):
    # type: (ConnectorSet, XYZ) -> Connector
    """Get connector from connector set iterable closest to an XYZ point"""
    min_distance = float("inf")
    closest_connector = None
    for connector in connectors:
        distance = connector.Origin.DistanceTo(xyz)
        if distance < min_distance:
            min_distance = distance
            closest_connector = connector
    return closest_connector

Connecting 2 connectors

This is actually really easy. Every connector has a Connector.ConnectTo(<other_connector>) method. This method is very permissive both connectors do not need to be at the same place which is a good thing. That’t how you can connect lights, plugs etc… to their panel without any physical connection. And it can lead to funny things :

How many architect have dreamed of wireless piping and ventilation ?

Physical connection

Logical connection only for duct and pipes is funny but also unstable and it is not what we want to achieve right ? So we also want them to be physically connected.

Each connectors has it’s own coordinate system. If you do families you have probably noticed that if you don’t set it correctly it leads to strange behaviour. Basis X and Y define the plane to land the other connector. Basis Z show in which direction connection is made. When 2 connectors are connected their origin has same coordinates, XY planes are coplanar and their Z basis are in opposite direction.

Rotate

We need to handle 3 cases :

  • connectors z basis are collinear and :
    • have opposite direction -> good, nothing to do.
    • have same direction -> not good, we need to rotate it of 180° (π) on their X or Y axis.
  • connectors coordinate systems are not collinear -> not good, we need to determine the angle and axis to make them collinear and in opposite direction. To determine the angle XYZ has an handy method xyz1.AngleTo(xyz2) which return the angle between 2 vectors, we then just need add or subtract π. To determine the axis we use xyz1.CrossProduct(xyz2) which return an XYZ (vector) perpendicular to the plane defined by the 2 XYZ.
# If connector direction is same, rotate it
angle = moved_direction.AngleTo(target_direction)
if angle != pi:
    if angle == 0:
        vector = moved_connector.CoordinateSystem.BasisY
    else:
        vector = moved_direction.CrossProduct(target_direction)
    try:
        line = Line.CreateBound(moved_point, moved_point+vector)
        moved_element.Location.Rotate(line, angle - pi)
    # Revit don't like angle and distance too close to 0
    except Exceptions.ArgumentsInconsistentException:
        logger.debug("Vector : {} ; Angle : {}".format(vector, angle))

Move

Easy part. We just need to translate one of the object by the difference of both connectors origin.

moved_element.Location.Move(target_connector.Origin - moved_connector.Origin)

Prompt user and finalize

We can now ask user to select the object+connector he wants to connect and the target object+connector.

PickObject and awesome pyRevit WarningBar

PickObject from Autodesk.Revit.UI.Selection namespace do not return an element as we could expect but a Reference. In our case, it is pretty useful as a Reference has a GlobalPoint property which stores where (XYZ) the user clicked on the object. Then we just use our get_connector_closest_to function.

I also wanted to point out one of the pyRevit feature introduced in 4.5 : the WarningBar which is a much more visible way to show user what to do. It looks like this :

pyRevit WarningBarIt can be used very easily with in a with statement. It shows up when you enter and vanishes when you exit :

with forms.WarningBar(title="Pick element to move and connect"):
    reference = uidoc.Selection.PickObject(ObjectType.Element, NoInsulation(), "Pick element to move")

Facilitate selection

You maybe noticed the NoInsulation ISelectionFilter I introduced as a parameter in PickObject prompt. It just prevent user from selecting insulation and objects that don’t have a connector manager.

class NoInsulation(ISelectionFilter):
    def AllowElement(self, elem):
        if isinstance(elem, InsulationLiningBase):
            return False
        try:
            get_connector_manager(elem)
            return True
        except AttributeError:
            return False

    def AllowReference(self, reference, position):
        return True

Video demo and full code

As always you can take a look at full source code under GPLv3 License on pyRevitMEP repository.

Best wishes for 2018 and pyRevitMEP extension update for pyRevit 4.5

pyRevit 4.5 has been released on 01.01.2018 with huge improvements. Make sure to check Ehsan post if you are interested to discover main new features.

Some change breaking have required some change on pyRevitMEP but thanks to Ehsan explanation videos and docs it was a piece of cake.

On an other hand, I added many new features to pyRevitMEP (some in labs) but I lack of time to write articles on new features at the moment.

To get all updates Make sure to :

  1. Uninstall previous version of pyRevit (strongly recommended according to Ehsan post)
  2. Install pyRevit 4.5 from official installer (or manually with git if you like)
  3. Install pyRevitMEP from extension manager (or clone it from github)

I wish all the best for 2018 and hope that we’ll all get a little more open source code, engineering, agriculture etc…

Check http://opensourceecology.org/ they do great things !

[pyRevit] Move labels in annotation families to origin (0,0)

Some very simple script can help you achieve tasks of missing features in Revit. As much as I know there is no way to accurately place a label to origin manually. A way to place it as well as possible is to put 0 as sample value and try to align it as well as possible by zooming. Sometimes when you activate a leader your line isn’t straight even if your annotation is perfectly perpendicular to your object just because your annotation family is not perfectly aligned on 0,0. The following very simple script will solve it your annotation family will be perfectly aligned on 0,0 :

# coding: utf8
import rpw
from rpw import DB

__doc__ = "Designed for annotation families. It moves selected annotation to center (set 0,0,0 coordinates)"
__title__ = "Center Text"
__author__ = "Cyril Waechter"
__context__ = 'Selection'

with rpw.db.Transaction():
 for text_element in rpw.ui.Selection():
 text_element.Coord = DB.XYZ()

The tool is available in pyRevitMEP. Just update it from pyRevit tab.