Skip to content

BH.Engine ‐ Create New Algorithms

Arnaud Declercq edited this page Dec 27, 2018 · 7 revisions

Repo Structure

The BHoM Engine repo contains all the functions and algorithms that process BHoM objects. Looking into how the Visual Studio solution is organised, we can see that it contains multiple projects.

One of them is the BHoM_Engine project (we are not very imaginative with our naming conventions). This project contains everything that allow for basic direct processing of BHoM objects. The other projects are designed around a set of algorithms focused on a specific area such as geometry, form finding, model laundry or even a given discipline such as structure.

As for the BH.oM itself, the BHoM Engine is split in many projects. The reason behind this is to allow for a large number of people to be able to work simultaneously on different parts of the code. Indeed, every time a file is added, deleted or even moved, this changes the project file itself. Submitting code to Github can become really painful when multiple people have modified the same files. Splitting code per project therefore limit the need to coordinate changes to the level of each focus group.

Folder Structure

If we look inside each project, we can see that folders are organised per action. If we take the Geometry_Engine project for example, we then find the 5 sub-folders Compute, Convert, Create, Modify, and Query. Those action names should be the same in all projects. We can then find in each folder C# files with names that correspond to the target of this action.

For more detail see Namespace Naming Conventions

File Structure

If we now look into a file, we can see that the namespace always starts with BH.Engine followed by the project name (without the _Engine obvioulsy). The folder name is then used as the name for the class. The name of the file is not directly used per se but will generally reflect the name of the methods defined in it. This is a bit of an unusual structure for those used to object-oriented programming so let's look at this in a bit more details.

namespace BH.Engine.Geometry
{
    public static partial class Query
    {
        /***************************************************/
        /**** Public Methods - Vectors                  ****/
        /***************************************************/

        public static Point ClosestPoint(this Point pt, Point point) {...}

        /***************************************************/

        public static Point ClosestPoint(this Vector vector, Point point) {...}

        /***************************************************/

        public static Point ClosestPoint(this Plane plane, Point point) {...}


        /***************************************************/
        /**** Public Methods - Curves                   ****/
        /***************************************************/

        public static Point ClosestPoint(this Arc arc, Point point) {...}

        /***************************************************/

        public static Point ClosestPoint(this Circle circle, Point point) {...}

        /***************************************************/

        ...
    }
}

As stated before, the BHoM Engine is nothing more than a big collection of functions. You can compare them to the collection of components you find in Grasshopper. As such, we do not need class definitions. Since C# doesn't provide the possibility to declare functions outside a class, we define them as static partial classes. That way, they can be treated as the last part of the namespace. For those that are not fluent in C#, static means that the content of the class is available without the need to create an object of that class. partial means that the full content of that class can be spread between multiple files. Inside that class, we can now define all the functions we need as long as we declare them as static as well.

Class Structure

For those that want to get stuck in but find the following sections a bit too technical, here's the general rules of how a class is organised:

  • As mentioned above, a class is always partial and static
  • Inside that class, create a function for each type of object you want to be able to handle. Notice that all the methods have the same name and possibly additional parameters, the only difference is the type of the first argument and possibly the return type.
  • Write this in front of the first argument of each function. This will for example allow to call the methods shown above using myArc.ClosestPoint(refPoint). This will be discussed in further details in the next section.
  • If you find yourself typing the same code for multiple functions (or even inside the same function), you can still create private static methods. Just make sure you place them in a separate private section (use same 3 line comment) after the public methods. In rare cases, you might also want to have your own private data structure for convenience. If that data structure will never be used elsewhere, just define it at the end of the class.
namespace BH.Engine.Geometry
{
    public static partial class Modify
    {
        /***************************************************/
        /**** Public Methods                            ****/
        /***************************************************/

        public static Mesh MergeVertices(this Mesh mesh, double tolerance = 0.001) //TODO: use the point matrix {...}


        /***************************************************/
        /**** Private Methods                           ****/
        /***************************************************/

        private static void SetFaceIndex(List<Face> faces, int from, int to) {...}


        /***************************************************/
        /**** Private Definitions                       ****/
        /***************************************************/

        private struct VertexIndex {...}
    }
}

While you might be able to write code in the BHoM Engine for a time without needing more than what has been explained so far, you should try to read the rest of the page. The concepts presented there are a bit more advanced but will however allow you to provide a better experience to those using your code. The keyword dynamic will also likely get you out of problematic situations when you are using code from people that have not read the rest of this page.

Extension Methods

A concept that is very useful in order to improve the use of your methods is the concept of extension methods. You can see on the example code below that we get the bounding box of a set of mesh vertices (i.e. a List of Points) by calling mesh.Vertices.Bounds(). Obviously, the List class doesn't have a Bounds method defined in it. The same goes for the BHoM objects; they even don't contain any method at all. The definition of the Bound method is actually in the BHoM Engine. In order for any BHoM objects (and even a List) to be able to call self.Bounds(), we use extension methods. Those are basically injecting functionality into an object from the outside. Let's look into how they work:

namespace BH.Engine.Geometry
{
    public static partial class Query
    {
        ...

        /***************************************************/
        /**** public Methods - Others                  ****/
        /***************************************************/

        public static BoundingBox Bounds(this List<Point> pts) {...}

        /***************************************************/

        public static BoundingBox Bounds(this Mesh mesh)
        {
            return mesh.Vertices.Bounds();
        }

        /***************************************************/

        ...

    }
}

Here is the properties of the Mesh object for reference:

namespace BH.oM.Geometry
{
    public class Mesh : IBHoMGeometry
    {
        /***************************************************/
        /**** Properties                                ****/
        /***************************************************/

        public List<Point> Vertices { get; set; } = new List<Point>();

        public List<Face> Faces { get; set; } = new List<Face>();


        /***************************************************/
        /**** Constructors                              ****/
        /***************************************************/

        ...
    }
}

Notice how each method has a this in front of their first parameter. This is all that is needed for a static method to become an extension method. Note that we can still calculate the bounding box of a geometry by calling BH.Engine.Geometry.Query.Bounds(geom) instead of geom.Bounds() but this is far more cumbersome.

To be complete, we should also mention that we could simply call Query.Bounds(geom) as long as using BH.Engine.Geometry is defined at the top of the file.

Polymorphism

While not completely necessary to be able to write methods for the BHoM Engine, Polymorphism is still a very important concept to understand. Consider the case where we have a list of objects and we want to calculate the bounding box of each of them. We want to be able to call Bounds() on each of those object without having to know what they are. More concretely, let's consider we want to calculate the bounding box of a polycurve. In order to do so, we need to first calculate the bounding box of each of its sub-curve but we don't know their type other that it is a form of curve (i.e. line, arc, nurbs curve,...). Note that ICurve is the interface common to all the curves.

namespace BH.Engine.Geometry
{
    public static partial class Query
    {
        ...

        /***************************************************/

        public static BoundingBox Bounds(this PolyCurve curve)
        {
            List<ICurve> curves = curve.Curves;

            if (curves.Count == 0)
                return null;

            BoundingBox box = Bounds(curves[0] as dynamic);
            for (int i = 1; i < curves.Count; i++)
                box += Bounds(curves[i] as dynamic);

            return box;
        }

        /***************************************************/

        ...

    }
}

Polymorphism, as defined by Wikipedia, is the provision of a single interface to entities of different types. This means that if we had a method Bounds(ICurve curve) defined somewhere, thanks to polymorphism, we could pass it any type of curve that inherits from the interface ICurve.

The other way around doesn't work though. If you have a series of methods implementing Bounds() for every possible ICurve, you cannot call Bounds(ICurve curve) and expect it to work since C# has no way of making sure that all the objects inheriting from ICurve will have the corresponding method. In order to ask C# to trust you on this one, you use the keyword dynamic as shown on the example above. This tells C# to figure out the real type of the ICurve during execution and call the corresponding method.

Polymorphic Extension Methods

Alright. Let's summarize what we have learnt from the last two sections:

  • Using method overloading (all methods of the same name taking different input types), we don't need a different name for each argument type. So for example, calling Bounds(obj) will always work as long as there is a Bounds methods accepting the type of obj as first argument.

  • Thanks to extension methods, we can choose to call a method like Bound by either calling Query.Bounds(obj) or obj.Bounds().

  • Thanks to the keyword dynamic, we can call a method providing an interface type that has not been explicitly covered by a method definition. For example, We can call Bounds on an ICurve even if Bounds(ICurve) is not defined.

Great! We are still missing one case though: what if we want to call obj.Bounds() when obj is an ICurve? So on the example of the PolyCurve provided above, what if we wanted to replace

box += Bounds(curves[i] as dynamic); 

with

box += curves[i].Bounds();

But why? We have a perfectly valid way to call Bounds on an ICurve already with the first solution. Why the need for another way? Same thing as for the extention methods: it is more compact and being able to have auto-completion after the dot is very convenient when you don't know/remember the methods available.

So if you want to be really nice to the people using your methods, there is a solution for you:

namespace BH.Engine.Geometry
{
    public static partial class Query
    {
        ...
        
        /***************************************************/
        /**** Public Methods - Interfaces               ****/
        /***************************************************/

        public static BoundingBox IBounds(this IBHoMGeometry geometry)
        {
            return Bounds(geometry as dynamic);
        }
    }
}

If you add this code at the end of your class, this code will now work:

ICurve curve = ...;
curve.IBounds();

Two comments on that:

  • We used IBHoMGeometry here because every geometry implements Bounds, not just the ICurves. ICurve being a IBHoMGeometry, it will get access to IBounds(). (Read the section on polymorphism again if that is not clear to you why). In the case of a method X only supporting curves such as StartPoint for example, our interface method will simply be StartPoint(ICurve).
  • The "I" in front of IBounds() is VERY IMPORTANT. If you simply call that method Bounds, it will have same name as the other methods with specific type. Say you call this method with a geometry that doesn't have a corresponding Bounds method implemented so the only one match is Bounds(IBHoMGeometry). In that case, Bounds(IBHoMGeometry) will call itself after the conversion to dynamic. You therefore end up with an infinite loop of the method calling itself.

PS: before anyone asks, using ((dynamic)curve).Bounds(); is not an option. Not only it crashes at run-time (dynamic and extension methods are not supported together in C#), it will not provides you with the auto completion you are looking for since the real type cannot be know statically.

What About Execution Speed ?

For the most experienced developers among you, some might worried about execution speed of this solution. Indeed, we are not only using extension methods but also the conversion to a dynamic object. This approach means that every method call of objects represented by an interface is actually translated into two (call to the public polymorphic methods and then to the private specific one).

Thankfully, tests have shown that efficiency lost is minimal even for the smallest functions. Even a method that calculates the length of a vector (1 square root, 3 multiplications and 2 additions) is running at about 75% of the speed, which is perfectly acceptable. As soon as the method become bigger, the difference becomes negligible. Even a method as light as calculating the length of a short polyline doesn't show more than a few % in speed difference.

Clone this wiki locally