-
Notifications
You must be signed in to change notification settings - Fork 16
The BHoM Toolkit
This page explains how to develop what we call a Toolkit.
Before reading this page, please check out:
- What is a Toolkit?
- Create a new software Toolkit using the BHoM Toolkit Template
- Implement the Adapter
- Set up a repo without using the template (not recommended)
A Toolkit is a Visual Studio solution that can contain one or more of the following:
- A BHoM_Adapter project, that allows to implement the connection with an external software.
- A BHoM_Engine project, that should contain the Engine methods specific to your Toolkit.
- A BHoM_oM project, that should contain any oM class (types) specific to your Toolkit.
In order to implement a new Toolkit, we prepared a Toolkit Template that does all the scaffolding for you: create an new Toolkit using the BHoM Toolkit Template.
Once you have created the Visual Studio solution using the template, you only need to implement the Adapter, and any Engine method and/or oM class that the Adapter should be using.
Let's get started!
This will install the Template in the system and it is required only once per machine.
Take SoftwareName_Toolkit.zip from the documentation
repo (documentation\templates\Toolkit template
) and copy it over to your Visual Studio ProjectTemplates
folder, generally in: C:\userName\Documents\Visual Studio 20xx\Templates\ProjectTemplates
Reboot Visual Studio.
-
Open Visual Studio. Do File --> New Project and search for "BHoM". Select the template "BHoM Toolkit Template".
-
If Visual Studio displays the checkbox "Place solution and project in the same directory" (VS version 2019 onwards), select it:
(For all other VS versions, just make sure that the checkbox "Create directory for solution" is ticked))
-
Specify your "SoftwareName" as the name of the solution. PascalCase, no spaces.
-
As parent root folder, specify the folder where you keep all other BHoM repos (generally, your GitHub folder
C:\Users\userName\GitHub\
). -
Confirm to create the new Toolkit solution.
This will result in a folder under your GitHub folder, such as ...\GitHub\SoftwareName
, with the template code inside, and the solution will open in Visual Studio.
Once the solution is created:
-
Close Visual Studio.
-
(Applies only if you couldn't do step 1. "Place solution and project in the same directory")
Go in the repo folder, which will be called "SoftwareName". It will contain only the solution file "SoftwareName.sln" and another folder "SoftwareName".
Enter in this "SoftwareName" folder, take all the folders and files and move them one folder up, so they sit together with the .sln file.
Delete the resulting empty folder "SoftwareName".
-
Rename the solution name and the folder name:
7.1. Add "_Toolkit" to the solution name
7.2. Add "_Toolkit" to the toolkit containing folder name.
-
Open the solution again.
If you could do step 1. "Place solution and project in the same directory", everything will be working and you are good to go.
Otherwise, if you couldn't do step 1, you will get "Project loading errors" due to the folder renaming of step 7. To correct:
In Solution Explorer, right click each project and do "Remove". Then right-click on the Solution name and do "Add Existing Project". Browse and select the
SoftwareName_Adapter
,SoftwareName_Engine
andSoftwareName_oM
projects in their new location.
The template creates the correct file names, class names and namespaces to be used for you.
The main Adapter file sits in the root of the Adapter project and must have a name in the format :SoftwareName_ Adapter.cs
.
The content of this file should be limited to the following items:
- The constructor of the Adapter. You should always have only one constructor for your Adapter.
You may add input parameters to the constructor: these will appear in any UI when an user tries to create it.
The constructor should define some or all of the Adapter properties:- the Adapter Settings;
- the Adapter Dependency Types;
- the Adapter Comparers;
- the AdapterIdName;
- any other protected/private property as needed.
- A few protected/private fields (methods or variables) that you might need share between all the Adapter files (given that the Adapter is a
partial
class, so you may share variables across different files). Please limit this to the essential.
The CRUD folder should contain all the needed CRUD methods.
The CRUD methods implementation details can be seen in their dedicated page.
Here we will cover a convention that we use: the CRUD "interface methods".
In the template, you can see how for all CRUD method there is an interface method called ICreate
, IRead
, etc.
These interface methods are the ones called by the adapter. You can then create as many CRUD methods as you want, even one per each object type that you need to create. The interface method is the one that will be called as appropriate by the Adapter Actions. From there, you can dispatch to the other CRUD methods of the same type that you might have created.
For example, in GSA_Toolkit you can find something similar to this:
protected override bool ICreate<T>(IEnumerable<T> objects, ActionConfig actionConfig = null)
{
return CreateObject((obj as dynamic));
}
The the statement CreateObject((obj as dynamic))
does what is called dynamic dispatching. It calls automatically other Create methods (called CreateObject
- all overloading each other) that take different object types as input.
The mapping from the Adapter Actions to the CRUD methods does need some help from the developer of the Toolkit.
This is generally done through additional methods and properties that need to be implemented or populated by the developer.
- Pushing of dependant objects
- Merging objects deemed to be the same
- Merging incoming objects with objects already existing in the model
- Applying an software specific 'id' to the objects being pushed
This is an important concept:
BHoM does not define a relationship chain between most Object Types.
This is because our Object Model aims to be as abstract and context-free as possible, so it can be applied to all possible cases.
If we were to define a relationship between all types, things would be more complicated than they already are. A typical scenario is the following. Some FE analysis software define Loads (e.g. weight) as independent properties, that can be Created first and then applied to some objects (for example, to a beam). Others require you to first define the object owning the Load (e.g. a beam), and then define the Load to be applied to it (the weight).
We can't have a generalised relationship between the beams and the loads, because not all external software packages agree on that. We should pick one. So instead, we pick none.
You can also avoid creating a relationship chain at all - if you are fine with exporting a flat collection of objects. You can activate/deactivate this Adapter feature by configure the Setting:
m_AdapterSettings.HandleDependencies
to true or false. If you enable this, you must implementDependencyTypes
as explained below.
We solve this situation by defining the DependencyTypes
property:
Dictionary<Type, List<Type>> DependencyTypes { get; }
This is a property of the single Adapter – that is, it can be different for different software connections.
The Toolkit developer should populate this accordingly to the inter-relationships that the BHoMObject hold in the perspective of the external software.
The Dictionary key is the Type for which you want to define the Dependencies; the value is a List of Types that are the dependencies.
An example from GSA_Toolkit:
DependencyTypes = new Dictionary<Type, List<Type>>
{
{typeof(BH.oM.Structure.Loads.Load<Node>), new List<Type> { typeof(Node) } },
...
}
The comparison between objects is needed in many scenarios, most notably in the Push, when you need to tell an old object from a new one.
In the same way that the BHoM Object model cannot define all possible relationships between the object types, it is also not possible to collect all possible ways of comparing the object with each other. Some software might want to compare two objects in a way, some in another.
You can also avoid creating a default comparers - if you are fine for the BHoM to use the default C# IEqualityComparer.
By default, if no specific Comparer is defined in the Toolkit, the Adapter uses the IEqualityComparers to compare the objects.
There are also some specific comparers for a few object types, most notably:
However you may choose to specify different comparers for your Toolkit. You must specify them in the Adapter Constructor.
An example from GSA_Toolkit:
AdapterComparers = new Dictionary<Type, object>
{
{typeof(Bar), new BH.Engine.Structure.BarEndNodesDistanceComparer(3) },
...
};
- The name of the visual studio solution and also the repository should be 'SoftwareName'_Toolkit.
- For the general case this solution will contain one or two projects:
- 'SoftwareName'_Adapter - Main project that will contain all methods for communication with the software.
- 'SoftwareName'_Engine - Optional project to host helper methods not requiring any connection with the application. Could be thing as converters and queries to get out specific information required for the software.
The _Adapter project should have the following folder and file structure (Example from the GSA_Adapter):
All files in this project should be part of a partial class called 'SoftwareName'Adapter. The main file .cs in the project called the same thing should be inheriting from the
BHoMAdapter
. The namespace used should beBH.Adapter.'SoftwareName'
(Example from gsa adapter):namespace BH.Adapter.GSA { public partial class GSAAdapter : BHoMAdapter { //Basic code for the adapter constructor, local fields and properties in this file } }The engine project should follow the guidlines outlined here: the Engine
Namespace for this repo should be
BH.Engine.'SoftwareName'
-
Introduction to the BHoM:
What is the BHoM for?
Structure of the BHoM
Technical Philosophy of the BHoM -
Getting Started:
Installing the BHoM
Using the BHoM
Submitting an Issue
Getting started for developers -
Use GitHub & Visual Studio:
Using the SCRUM Board
Resolving an Issue
Avoiding Conflicts
Creating a new Repository
Using Visual Studio
Using Visual Studio Code -
Contribute:
The oM
The Engine
The Adapter
The Toolkit
The UI
The Tests -
Guidelines:
Unit convention
Geometry
BHoM_Engine Classes
The IImmutable Interface
Handling Exceptional Events
BHoM Structural Conventions
BHoM View Quality Conventions
Code Versioning
Wiki Style
Coding Style
Null Handling
Code Attributes
Creating Icons
Changelog
Releases and Versioning
Open Sourcing Procedure
Dataset guidelines -
Foundational Interfaces:
IElement Required Extension Methods -
Continuous Integration:
Introduction
Check-PR-Builds
Check-Core
Check-Installer -
Code Compliance:
Compliance -
Further Reading:
FAQ
Structural Adapters
Mongo_Toolkit
Socket_Toolkit