Tag Archives: pyRevit

[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 !

[Revit] Batch view renaming, regular expression, docstrings, a GUI

This batch renaming utility elaboration has been a long journey. I did a simple one long time ago which was supporting only 1 renaming pattern : http://pythoncvc.net/?p=27

Principle

We use parameters available in our views to rename them in a programmatic and logic way. My old script was using an home parameter for discipline and sub-discipline which is set by view template, level and scope box name to make it unique as much as possible.

Objectives

Flexibility : clients, contractors or someone else may ask a specific naming pattern for views. So pattern need to be easily adaptable and savable.

Multilingual : as always, to avoid language dependent behaviour, when it is possible built in parameter name or shared parameter Guid shall be used to retrieve values.

Human readable : parameters storage type may be text, number, id etc… and have a better human readable AsValueString (it is the case for scope boxe).

Free separators choice : we should be able to freely choose and change separator, prefix, suffix between parameters

Flexible selection of view to rename : be able to rename X views then select Y other views and rename it and so on.

Code behind in short

Renaming part

I was thinking about common file re-namers like Ant Renamer. User is able to type parameters as a simple string and add anything he wants between them. The string looks like the old string formatting style with %s %d etc…

So my first attempt was to use python string format like "string{Guid.<Guid>}_{bip.<BuiltInParameterName>_{<parameter_name>}".format(parameter_by_Guid.value, parameter_by_builtinparameter.value, parameter_by_name.value). That was working but somehow it felt weird and slow.

I finally used regular expressions which are very much used for this kind of task in a very efficient way (spelling correction, retrieve a phone number, verify that an email entered by a user looks like one etc…).

So we needed a regular expression to retrieve parameters by Guid :

# Create a regular expression to retrieve Guid (including constructor) in a string
parameterGuidRegex = re.compile(r'''
Guid\( # Guid constructor. Example : Guid("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4")
[\" | \'] # Single or double quote required in Guid constructor
([0-9a-f]{8} # First 8 hexa
(-?[0-9a-f]{4}){3} # Optional separator + 4 hexa 3 times
-?[0-9a-f]{12}) # Optional separator + 12 hexa
[\" | \'] # Single or double quote required in Guid constructor
\) # Close parenthesis necessary to build a Guid
''', re.IGNORECASE | re.VERBOSE)

… by BuiltInParameter (way far simpler) :

# Create a regular expression to retrieve BuiltInParameter in a string
parameterBipRegex = re.compile(r"bip\((\w+)\)")

… and finally by name :

# Create a regular expression to retrieve named parameters in a string
parameterNameRegex = re.compile(r"name\(([\w\s]+)\)") # Retrieve group(1)

There is mainly 3 class of view (excluding schedules, legends etc…) : ViewPlan, View3D, ViewSection. Each kind don’t have the same available parameter. For example there is no level parameter  in 3D and section views. So 3 pattern definition was required to batch rename views. To show available parameters to user a sample view of each class is retrieved and parameters Guid, BuiltInParameterName, and Name are stored as a dictionary.

To help user add parameter to pattern the best way. A function try to retrieve Guid else BuiltInParameter name else name.

Another function replace pattern by their associated parameter name and value to show user a preview.

A last function is in charge of actually renaming views.

GUI part

A WPF GUI splitted in 4 part, 3 for each view classes, 1 for saving and validation options.

Saving/loading part

Thanks again to Ehsan, there is a very easy way to save script parameter to pyRevit config file. Don’t forget to encode and decode your string !

my_config = this_script.config
# Then in WPF class we have following method :

def save_config_click(self, sender, e):
 for view_class in self.view_class_dict.keys():
 view_class_checkbox = eval('self.cb_{}'.format(view_class))
 if view_class_checkbox.IsChecked:
 pattern_textbox = eval('self.{}_pattern'.format(view_class))
 setattr(my_config, view_class, pattern_textbox.Text.encode('utf8'))
 this_script.save_config()

But I wanted also to be able to save to a parameter for project specific naming rules. Unfortunately with Revit API it is still currently not possible to create a project parameter which is not shared. So based on Spiderinnet samples I wrote a function to create a shared parameter and store configuration in it as a string which is formatted in a dictionary/json style.

When script is launched. It tries to load configuration from project parameter (as project specific case should be use first) else from pyRevit config file (the way you name it on your office) else from default values (which are for most people just samples).

Modeless / ExternalEvent part

To be able to rename successively multiple selections of views it was mandatory to use a modelless WPF window and so to use ExternalEvent for any Transaction to Revit : create shared parameter, save to parameter, rename views. I described how to do so in a previous article but as there was 3 different action to make it was a good idea to enhance it. I made 2 class to be able to execute any function or method through the external event using *args and *kwargs :

class CustomizableEvent:
 def __init__(self):
 self.function_or_method = None
 self.args = ()
 self.kwargs = {}

 def raised_method(self):
 self.function_or_method(*self.args, **self.kwargs)

 def raise_event(self, function_or_method, *args, **kwargs):
 self.args = args
 self.kwargs = kwargs
 self.function_or_method = function_or_method
 custom_event.Raise()


customizable_event = CustomizableEvent()


# Create a subclass of IExternalEventHandler
class CustomHandler(IExternalEventHandler):
 """Input : function or method. Execute input in a IExternalEventHandler"""

 # Execute method run in Revit API environment.
 # noinspection PyPep8Naming, PyUnusedLocal
 def Execute(self, application):
 try:
 customizable_event.raised_method()
 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 an handler instance and his associated ExternalEvent
custom_handler = CustomHandler()
custom_event = ExternalEvent.Create(custom_handler)

Full source code

As usual full source code is available on github.

Video demo

To learn more about

Thanks to all these resources which helped a lot

WPF tutorial :

http://www.wpf-tutorial.com/

string format :

https://pyformat.info/

Regular expressions :

https://www.udemy.com/automate/learn/v4/overview

https://automatetheboringstuff.com/chapter7/

https://docs.python.org/3.10/library/re.html

https://www.pcwdld.com/python-regex-cheat-sheet

Encoding :

http://sametmax.com/lencoding-en-python-une-bonne-fois-pour-toute/

Docstring :

http://sametmax.com/lencoding-en-python-une-bonne-fois-pour-toute/

https://www.python.org/dev/peps/pep-0257/

*args and **kwargs :

http://deusyss.developpez.com/tutoriels/Python/args_kwargs/

[pyRevit] pyRevitMEP is now available in pyRevit extensions manager

Thanks to his creator Ehsan Iran-Nejad and all credits we have a great tool named pyRevit. This tool allows us to easily make python scripts for Revit and distribute them via the extensions manager.

And it is exactly what I did by creating pyRevitMEP extension which has been added by Ehsan to pyRevit extensions manager last week-end :

pyRevitMEP

pyRevitMEP philosophy : encourage MEP people to group-up and develop common tools. You want to contribute by coding : send a pull request on github or more (do not hesitate to contact me). If you prefer to manage your own extension, add your tools in the /pyRevitMEP.tab/ and follow instructions on pyRevit blog to add it to extensions manager. You want to contribute but don’t know how, check FAQ page. License is and will remain open source. It is currently under GNU GPL v3.0 License.

[Revit] Change objects reference level without moving it (Updated with a GUI)

Here is a new update of my [Revit] Change fittings reference level without moving it with :

  1. Updated to be used in pyRevit
  2. A new WPF GUI with a new option to select a level from your project list
  3. Better error handling which makes it easier to use

Find the short video tutorial here :

Full source code available here : https://github.com/CyrilWaechter/pyRevitMEP/tree/master/pyRevitMEP.tab/Tools.panel/ElementChangeLevel.pushbutton

Thanks a lot to :

  • AngelSix for his great WPF tutorial
  • Gui Talarico for his great «SelectFromList» example available in his RevitPythonWrapper

[Revit] Rotate elements in any direction script

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 !

[Revit API] Simple Modeless Form (External Event Handler) in pyRevit

Warning : Some change in Revit API and Revit behaviour makes this article obsolete with newer version of Revit. I advise you to install pyRevitMEP extension and study updated code : FormExternalEventHandler.pushbutton. For usage in your script I recommend to use my CustomizableEvent from pyRevitMEP library.

I struggled for a while to make a modeless form. Why did I need it ? Because each time I was trying to get user to select object after WPF appear I was going out of Revit API thread and got this very common exception «Autodesk.Revit.Exceptions.InvalidOperationException: Attempting to create an ExternalEvent outside of a standard API execution». As Jeremy Tammik says :

One of the most frequently raised questions around the Revit API is how to drive Revit from outside, e.g., from a separate thread, a modeless dialogue, or a stand-alone executable.

I have read many examples on the subject. Most on them were in C#.

So I made a very simple form to make a very simple ExternalEventHandler sample as pyRevit script. It will help me and I hope it will help some hackers to struggle less than I did.

Let’s start with common import statement using built-in pyRevit utils :

# noinspection PyUnresolvedReferences
from Autodesk.Revit.UI import IExternalEventHandler, ExternalEvent
# noinspection PyUnresolvedReferences
from Autodesk.Revit.DB import Transaction
# noinspection PyUnresolvedReferences
from Autodesk.Revit.Exceptions import InvalidOperationException
from revitutils import selection, uidoc, doc
from scriptutils.userinput import WPFWindow

__doc__ = "A simple modeless form sample"
__title__ = "Modeless Form"
__author__ = "Cyril Waechter"

Then let’s write a simple function we want to execute modeless  (here it just delete selected elements) :

# Simple function we want to run
def delete_elements():
    t = Transaction(doc, "Failing script")
    t.Start()
    for elid in uidoc.Selection.GetElementIds():
        print elid
        doc.Delete(elid)
    t.Commit()

And now come the new magic thing that let you enter in a valid Revit API context. The «ExternalEvent» class with his «IExternalEventHandler» class :

# Create a subclass of IExternalEventHandler
class SimpleEventHandler(IExternalEventHandler):
    """
    Simple IExternalEventHandler sample
    """

    # __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.
    def Execute(self, uiapp):
        try:
            self.do_this()
        except InvalidOperationException:
            # If you don't catch this exeption Revit may crash.
            print "InvalidOperationException catched"

    def GetName(self):
        return "simple function executed by an IExternalEventHandler in a Form"


# Now we need to make an instance of this handler. Moreover, it shows that the same class could be used to for
# different functions using different handler class instances
simple_event_handler = SimpleEventHandler(delete_elements)
# We now need to create the ExternalEvent
ext_event = ExternalEvent.Create(simple_event_handler)

Let’s do a simple form so easily created thanks to pyRevit in order to use our new toy :

# A simple WPF form used to call the ExternalEvent
class ModelessForm(WPFWindow):
    """
    Simple modeless form sample
    """

    def __init__(self, xaml_file_name):
        WPFWindow.__init__(self, xaml_file_name)
        self.simple_text.Text = "Hello World"
        self.Show()

    def delete_click(self, sender, e):
        # This Raise() method launch a signal to Revit to tell him you want to do something in the API context
        ext_event.Raise()

# Let's launch our beautiful and useful form !
modeless_form = ModelessForm("ModelessForm.xaml")

Here is the xaml code :

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Title="Delete things:" Height="150" Width="300" ShowInTaskbar="False" Topmost="True"
 WindowStartupLocation="CenterScreen" ScrollViewer.VerticalScrollBarVisibility="Disabled" HorizontalContentAlignment="Center">
 <StackPanel Margin="20" HorizontalAlignment="Stretch">
 <TextBlock x:Name="simple_text" Text="" Grid.Column="0" HorizontalAlignment="Center" FontWeight="Bold"/>
 <Button Content="Delete selected elements" Height="30" Margin="10,10" Click="delete_click"/>
 </StackPanel>
</Window>

Thanks a lot to all people mentioned in this article and linked article and stuffs.