diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..82686f71d --- /dev/null +++ b/404.html @@ -0,0 +1,4149 @@ + + + + + + + + + + + + + + + + + + + BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Adapter/Adapter-Actions---advanced-parameters/index.html b/BHoM_Adapter/Adapter-Actions---advanced-parameters/index.html new file mode 100644 index 000000000..24de70e0f --- /dev/null +++ b/BHoM_Adapter/Adapter-Actions---advanced-parameters/index.html @@ -0,0 +1,4384 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Adapter Actions: advanced parameters - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Adapter Actions: advanced parameters

+

The Adapter Actions have some optional inputs that allow to refine their behaviour.

+
+

Note

+

This page can be seen as an optional Appendix to the pages Introduction to BHoM_Adapter and Adapter Actions.

+
+

These optional inputs are:

+
    +
  • the ActionConfig (used by all Actions: Push, Pull, Move, Remove, Execute);
  • +
  • the Requests (used by the Pull)
  • +
  • the Data Tags (if implemented for the specific Adapter, they are used by: Push, Pull, Move, Remove)
  • +
+

ActionConfig

+

The ActionConfig is an object type used to specify any kind of Configuration that might be used by the Adapter Actions.

+

This means that it can contain configurations that are specific to certain Actions (e.g. only to the Push, only to the Pull), and that a certain Push might be activated with a different Push ActionConfig than another one. This makes the ActionConfig different from the Adapter Settings (which are static global settings).

+

The base ActionConfig provides some configurations that are available to all Toolkits (you can find more info about those in the code itself).

+

You can inherit from the base ActionConfig to specify your own in your Toolkit. For example, if you are in the SpeckleToolkit, you will be able to find: +- SpecklePushConfig: inherits from ActionConfig +- SpecklePullConfig: inherits from ActionConfig

+

this allows some data to be specified when Pushing/Pulling.

+

ActionConfig is an input to all Adapter methods, so you can reference configurations in any method you might want to override.

+



+

Requests

+

Requests are an input to the Pull adapter Action.

+

They were formerly called Queries and are exactly that: Queries. You can specify a Request to do a variety of things that always involve Pulling data in from an external application or platform. For example: +- you can Request the results of an FE analysis from a connected FEM software, +- specify a GetRequest when using the HTTP_Toolkit to download some data from an online RESTFul Endpoint +- query a connected Database, for example when using Mongo_Toolkit.

+

Requests can be defined in Toolkits to be working specifically with it.

+

You can find some requests that are compatible with all Toolkits in the base BHoM object model. +An example of those is the FilterRequest.

+

The FilterRequest is a common type of request that basically requests objects by some specified type. See FilterRequest.

+

In general, however, Requests can range from simple filters to define the object you want to be sent, to elaborated ones where you are asking the external tool to run a series of complex data manipulation and calculation before sending you the result.

+
+

Additional note: batch requests

+

For the case of complex queries that need to be executed batched together without returning intermediate results, you can use a BatchRequest.

+

Additional note: Mongo requests

+

For those that use Mongo already, you might have noticed how much faster and convenient it is to let the database do the work for you instead doing that in Grasshopper. It also speeds up the data transfer in cases where the result is small in bytes but involves a lot of data to be calculated.

+
+



+

Data Tags

+

When objects are pushed, it is important to have a way to know which objects needs to be Updated, the new ones to be Created, and the old ones to be Deleted.

+

If the number of objects changes between pushes, you cannot rely on unique identifiers to match the objects one-to-one. The problem is especially clear when you are pushing less objects than the last push.

+

Attaching a unique tag to all the objects being pushed as a group is a lightweight and flexible way to find those objects later.

+
+

For those using D3.js, this is similar to attaching a class to html elements. For those using Mongo or Flux, this is similar to the concept of key.

+
+

Tags in practice

+

At the moment, each external software will likely require a different solution to attach the tags to the objects.

+

If the software doesn't provide any solution to store the tag attached to the objects (e.g., like Groups), we could make use of another appropriate field to store the tag, for example the Name field that is quite commonly found.

+

In case you need to use the Name field of the external object model, the format we are using for that is (example for an object with three tags): +

Name __Tags__:tag1_/_tag2_/_tag3
+

+

For an in depth explanation on how tags are used and what you should be implementing for them to work, read the Push section of our Adapter Actions page; in particular, look at the practical example.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Adapter/Adapter-Actions/index.html b/BHoM_Adapter/Adapter-Actions/index.html new file mode 100644 index 000000000..0f89bb9db --- /dev/null +++ b/BHoM_Adapter/Adapter-Actions/index.html @@ -0,0 +1,4543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Adapter actions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Adapter actions

+

After covering the basics in Introduction to BHoM_Adapter, this page explains the Adapter Actions more in detail, including their underlying mechanism.

+

After reading this you should be all set to develop your own BHoM Toolkit! 🚀

+
+

Note

+

Before reading this page, make sure you have read the Introduction to BHoM_Adapter.

+
+

How the Adapter Actions work

+

As we saw before, the Adapter Actions are backed by what we call CRUD methods. Let's see what that means.

+

The CRUD paradigm

+

A very common paradigm that describes all the possible action types is CRUD. This paradigm says that, regardless of the connection being made, the connector actions can be always categorised as: +* Create = add new entries +* Read = retrieve, search, or view existing entries +* Update = edit existing entries +* Delete = deactivate, or remove existing entries

+

Some initial considerations:

+
    +
  • +

    Read and Delete are quite self-explanatory; regardless of the context, they are usually quite straightforward to implement.

    +
  • +
  • +

    Create and Update, on the other hand, can sometimes overlap, depending on the interface we have at our disposal (generally, the external software API can be limiting in that regard).

    +
  • +
  • +

    Exposing directly these methods would make the User Experience quite complicated. Imagine having to split the various objects composing your model into the objects that need to be Created, the ones that needs to be Updated, and so on. Not nice.

    +
  • +
+

We need something simpler from an UI perspective, while retaining the advantages of CRUD - namely, their limited scope makes them simple to implement.

+

The answer is the Adapter Actions: they take care of calling the CRUD methods in the most appropriate way for both the user and the developer.

+

An example: the Push Action

+

Let's consider for example the case where we are pushing BHoM objects from Grasshopper to an external software.
+The first time those objects are Pushed, we expect them to be Created in the external software.
+The following times, we expect the existing objects to be Updated with the parameters specified in the new ones.

+
+

In detail: Why the "Actions-CRUD" paradigm?

+

This paradigm allows us to extend the capabilities of the CRUD methods alone, while keeping the User Experience as simple as possible; it does so mainly through the Push. The Push, in fact, can take care for the user of doing Create or Update or Delete when most appropriate – based on the objects that have been Read from the external model.

+

The rest of the Adapter Actions mostly have a 1:1 correspondence with the backing CRUD methods; for example, Pull calls Read, but its scope can be expanded to do something in addition to only Reading. This way, Read is "action-agnostic", and can be used from other Adapter Actions (most notably, the Push). You write Read once, and you can use it in two different actions!

+

Side note: Why using five different Actions (Push, Pull, Move, Remove, Execute)...

+

... and not something simpler, like "Export" and "Import"?
+... or just exposing the CRUD methods?
+The reason is that the methods available to the user need to cover all possible use cases, while being simple to use. +We could have limited the Adapter Actions to only Push and Pull – that does in fact correspond to Export and Import, and are the most commonly used – but that would have left out some of the functionality that you can obtain with the CRUD methods (for example, the Deletion).

+

On the other hand, exposing directly the CRUD methods would not satisfy the criteria of simplicity of use for the User. +Imagine having to Read an external model, then having manually to divide the objects in the ones to be Updated, the ones to be Deleted, then separately calling Create for the new ones you just added... Not really simple! The Push takes care of that instead.

+

Side note: Other advantages of the "Actions-CRUD" paradigm

+

We've explained how this paradigm allows us to cover all possible use cases while being simple from an User perspective. In addition, it allows us to: +1) ensure consistency across the many, different implementations of the BHoM_Adapter in different Toolkits and contexts, therefore: +2) ensuring consistency from the User perspective (all UIs have the same Adapter Actions, backed by different CRUD methods) +3) maximise code scalability +4) Ease of development – learn once, implement everywhere

+
+

CRUD methods: details and implementation

+

The paragraphs that follow down below are dedicated to explaining the relationship between the CRUD methods and the Adapter Actions.

+

For first time developers, this is not essential – you just need to assume that the CRUD methods are called by the Adapter Actions when appropriate.
+You may now want to jump to our guide to build a BHoM Toolkit.

+
+

You will read more about the CRUD methods and how you should implement them in their dedicated page that you should read after the BHoM_Toolkit page.

+
+

Otherwise, keep reading.

+

Advanced topic (optional) - Adapter actions: complete description

+

We can now fully understand the Adapter Actions, complete of their relationships with their backing CRUD methods.

+

Push

+

The Push action is defined as follows: +

 public virtual List<object> Push(IEnumerable<object> objects, string tag = "", PushType pushType = PushType.AdapterDefault, ActionConfig actionConfig = null)
+

+

This method exports the objects using different combinations of the CRUD methods as appropriate, depending on the PushType input.

+

image

+

Let's see again how we described the Push mechanism in the previous page:

+
+

The Push takes the input objects and: + - if they don't exist in the external model yet, they are created brand new; + - if they exist in the external model, they will be updated (edited); + - under some particular circumstances and for specific software, if some objects in the external software are deemed to be "old", the Push will delete those.

+
+

The determination of the object status (new, old or edited) is done through a "Venn Diagram" mechanism: +img

+

The Venn Diagram is a BHoM object class that can be created with any Comparer that you might have for the objects. It compares the objects with the given rule (the Comparer) and returns the objects belonging to one of two groups, and the intersection (objects belonging to both groups).

+

During the Push, the two sets of objects being compared are the objects currently being pushed, or objectsToPush, and the ones that have been read from the external model, or existingObjects.

+

This is the reason why the first CRUD method that the Push will attempt to invoke is Read. The Push is an export, but you need to check what objects exist in the external model first if you want do decide what and how to export.

+
+

Additional note: custom Comparers

+

Once the existingObjects are at hand, it's easy to compare them with the objectsToPush through the Venn Diagram. Even if no specific comparer for the object has been written, the base C# IEqualityComparer will suffice to tell the two apart. If you want to have some specific way of comparing two objects (for example, if you think that two overlapping columns should be deemed the same no matter what their Name property is), then you should define specific comparer for that type. You can see how to do that in the next page dedicated to the BHoM_Toolkit.

+
+

A practical example

+

Now, let's think that we are pushing two columns: column A_new and column B_new; and that the external model has already two columns somewhere, column B_old and column C_old. B_new and B_old are located in the same position in the model space, they have all the same properties except the Name property.

+

We activate the Push.

+

First, the external model is read. The existingObjects list now includes the two existing columns B_old and C_old.

+

Then a VennDiagram is invoked to compare the existingObjects with the objectsToPush (which are the two pushed columns A_new and B_new).

+

1) The object being pushed is new.

+

There is no existing object in the external model that corresponds to one of the columns being pushed. Easy peasy: Push will call Create this column for this category of objects. A_new is Created.

+

2) The object being pushed is deemed the same of one in the external model.

+

What does "deemed the same" means?

+

It means that the Comparer has evaluated them to be the same. This does not exclude that there might be some property of the objects that the Comparer is deliberately skipping to compare.

+

For example, we might have a Comparer that says:

+
+

two overlapping columns should be deemed the same no matter what their Name property is.

+
+

If so, columns B_new and B_old are deemed the same.

+

But then, we need to update the Name property of the column in the external model, with the most up-to-date Name from the object being pushed.

+

Hence, we call Update for this category of objects.
+B_new is passed to the Update method.

+

3) Remaining existing objects that are not among the objects being pushed.

+

What to do with this category of objects? What to do with C_old?

+

An easy answer would be "let's Delete 'em!", probably. However, if we simply did that, then we would force the user to always input, in the objectsToPush, also all the objects that do not need to be Deleted.

+

Which is what we ask the user to do anyway, but to a lesser scale. +Our approach is not to do anything to these objects, unless tags have been used.

+

We assume that if the User wants the Delete method to be called for this category of objects, then the existing objects must have been pushed with a tag attached. If the tag of the objects being Pushed is the same of the existing objects, we deem those objects to be effectively old, calling Delete for them.

+

Let's imagine that our column C_old was originally pushed with the attached tag "basementColumns". +If I'm currently pushing columns with the same tag "basementColumns", it means that I'm pushing the whole set of columns that should exist in the basement. Therefore, C_old is Deleted.

+

Overlapping objects with multiple tags

+

Let's say that I push a set of columns with the tag "basementColumns". Everything that those bars need to be fully defined – what we call the Dependant Objects, e.g. the bar end nodes (points), the bar section property, the bar material, etc. – will be pushed together with it, and with the same tag attached.

+

Let's then say I then push another set of bars corresponding to an adjacent part of the building with the tag "groundFloorColumns".

+

It could be that a column with the tag "basementColumns" has an endpoint that overlaps with the endpoint of another column tagged "groundFloorColumns". That endpoint is going to have two tags: basementColumns groundFloorColumns.

+

The overlapping elements will end up with two tags on them: "basementColumns" and "groundFloorColumns".

+

Later, I do another push of columns tagged with the tag groundFloorColumns. +Some objects come up as existing only in the external model and not among those being pushed.
+Since a tag is being used and checks out, I should be deleting all these objects.
+However, the overlapping endpoint should not be deleted; simply, groundFloorColumns should be removed from its tags.

+

We then call the IUpdateTags method for these objects (no call to Delete). +That is a method that should be implemented in the Toolkit and whose only scope is to update the tags. Its implementation is left to the developer, but some examples can be seen in some of the existing adapters (GSA_Adapter).

+

A full diagram for 1), 2) and 3)

+

This diagram summarises what we've been saying so far for the Push.

+

image

+

Complete flow diagram of the Push (advanced)

+

Since an image is worth a thousand words, we provide a complete flow diagram of the Push below. If you click on the image you can download it.

+

This is really an advanced read that you might need only if you want to get into the nitty-gritty of the Push mechanism.

+

+ +

+

Pull

+

The Pull action is defined as follows: +

public virtual IEnumerable<object> Pull(IRequest request, PullType pullType = PullType.AdapterDefault, ActionConfig actionConfig = null)
+

+

This Action has a more 1:1 correspondence with the backing CRUD method: it is essentially a simple call to Read that grabs all the objects corresponding to the specified IRequest (which is, essentially, simply a query).
+There is some additional logic related to technicalities, for instance how we deal with different IRequests and different object types (IBHoMObject vs IObjects vs IResults, etc).

+

image

+

You can find more info on Requests in their related section of the Adapter Actions - Advanced parameters wiki page.

+

Note that the method returns a list of object, because the pulled objects must not necessarily be limited to BHoM objects (you can import any other class/type, also from different Object Models).

+

Move

+

Move performs a Pull and then a Push.

+

image

+

It's greatly helpful in converting a model from a software to another without having to load all the data in the UI (i.e., doing separately a Pull and then a Push), which would prove too computationally heavy for larger models.

+

image

+

Remove

+

The Remove action is defined as follows: +

int Remove(IRequest request, ActionConfig config = null);
+

+

This method simply calls Delete.

+

image

+

You might find some Toolkits that, prior to calling Delete, add some logic to the Action, for example to deal with a particular input Request.

+

The method returns the number of elements that have been removed from the external model.

+

Execute

+

The Execute is defined as follows:

+
public virtual Output<List<object>, bool> Execute(IExecuteCommand command, ActionConfig actionConfig = null)
+
+

The Execute method provides a way to ask your software to do things that are not covered by the other methods. A few possible cases are asking the tool to run some calculations, print a report, save,... A dictionary of parameters is also provided if needed. In the case of print for example, it might be the folder where the file needs to be saved and the name given to the file.

+

The method returns true if the command was executed successfully.

+

Next steps: Create Your Own Adapter

+

Read on our guide to build a BHoM Toolkit.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Adapter/Implement-an-Adapter/index.html b/BHoM_Adapter/Implement-an-Adapter/index.html new file mode 100644 index 000000000..32a51e8b3 --- /dev/null +++ b/BHoM_Adapter/Implement-an-Adapter/index.html @@ -0,0 +1,4614 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Implement an Adapter - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Implement an Adapter

+

An adapter can be implemented in order to add conversion features from BHoM to another software, and vice versa.

+

An adapter should be added to a dedicated Toolkit repository. See the page dedicated to the The BHoM Toolkit to learn how to set up a Toolkit, which can then contain an Adapter.

+
+

Warning

+

Before reading this page, please check out:

+ +
+

Main Adapter file and constructor

+

The main Adapter file sits in the root of the Adapter project and must have a name in the format SoftwareNameAdapter.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 Adapter Actions

+

Overriding the Adapter Actions

+

If you want, you can override one or more of the Adapter Actions. This can be useful for quick development.

+

All Action methods are defined as virtual, so you can override them.

+

In order to reuse the existing logic embedded in the Adapter Actions, you should not override them. This requires the implementation of CRUD methods which will be called by the Actions. Continue reading to learn more.

+

The Adapter Settings

+

The Adapter settings are general settings that can be used by the Adapter Actions and/or the CRUD methods.

+

You can define them as you want; just consider that the settings are supposed to stay the same across any instance of the same adapter, i.e. the Adapter Settings are global static settings valid for all instances of your Toolkit Adapter. In other words, these settings are independent of what Action your Toolkit is doing (unlike the ActionConfig). If you want to create settings that affect a specific action, implement an ActionConfig instead.

+

The base BHoM_Adapter code gives you extensive explanation/descriptions/comments about the Adapter Settings.

+

Implement the CRUD methods

+

The CRUD folder should contain all the needed CRUD methods.

+

You can see the CRUD methods implementation details in their dedicated page.

+

Here we will cover a convention that we use in the code organisation: 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.

+

Additional methods and properties

+

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
  • +
+

Dependency types

+

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.

+
+

Note: optional feature

+

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 implement DependencyTypes as explained below.

+
+

Dependency types in practice

+

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) } },
+    ...
+}
+

+

Comparers

+

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.

+
+

Note: optional feature

+

You can also avoid creating a default comparers - if you are fine for the BHoM to use the default C# IEqualityComparer.

+
+

Adapter Comparers in practice

+

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: +* Node comparer - by proximity +* BHoMObject name comparer

+

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) },
+                ...
+            };
+

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Adapter/Structural-Adapters/index.html b/BHoM_Adapter/Structural-Adapters/index.html new file mode 100644 index 000000000..d1835700d --- /dev/null +++ b/BHoM_Adapter/Structural-Adapters/index.html @@ -0,0 +1,4340 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Structural Engineering adapters - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Structural Engineering adapters

+

This page gives examples and outlines the general common behaviour of the adapters communicating with structural engineering software.

+

To get an general introduction to how the adapters are working, and how to implement a new one please see the set of wiki pages starting from Introduction to the BHoM Adapter.

+

Specific Structural Engineering adapters

+

For information regarding software specific adapter features, known issues and object relation tables, please see their toolkit wikis:

+ +

Pushing and pulling elements

+

Please see the samples for examples of how to push elements to a software using the adapters.

+

Pushing and pulling loads

+

The objects assigned to the loads need to have been in the software. The reason for this is that the objects need to have been tagged with a CustomData representing their identifier in the software. To achieve this you can

+
    +
  1. First push all the elements, then in a separate step pull them out again and sort out which elements that are applicable to be loaded. (Recomended workflow)
  2. +
  3. Use the objects output of the PushComponent. That adapter will have made sure that all objects coming out from the adapter will have been assigned with the correct tags.
  4. +
+

Please see the samples for examples of how to push elements to a software using the adapters.

+

Pulling results

+

Examples to be inserted

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Adapter/The-CRUD-methods/index.html b/BHoM_Adapter/The-CRUD-methods/index.html new file mode 100644 index 000000000..0e0129560 --- /dev/null +++ b/BHoM_Adapter/The-CRUD-methods/index.html @@ -0,0 +1,4561 @@ + + + + + + + + + + + + + + + + + + + + + + + + + The CRUD methods - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

◀️ Previous read: The BHoM Toolkit and Adapter Actions

+
+


+
+

Note

+

This page can be seen as an Appendix to the pages Adapter Actions and The BHoM Toolkit.

+
+

As we have seen, the CRUD methods are the support methods for the Adapter Actions. They are the methods that have to be implemented in the specific Toolkits and that differentiate one Toolkit from another.

+

Their scope has to be well defined, as explained below.

+

Note that the Base Adapter is constellated with comments (example) that can greatly help you out.

+

Also the BHoM_Toolkit Visual Studio template contains lots of comments that can help you.

+

Create

+

Create must take care only of Creating, or exporting, the objects. +Anything else is out of its scope.

+

For example, a logic that takes care of checking whether some object already exists in the External model – and, based on that, decides whether to export or not – cannot sit in the Create method, but has rather to be included in the Push. +This very case (checking existing object) is already covered by the Push logic.

+

The main point is: keep the Create simple. It will be called when appropriate by the Push.

+

The Create method in practice

+

The Create method scope should in general be limited to this: +- calling some conversion from BHoM to the object model of the specific software and a +- Use the external software API to export the objects.

+

If no API calls are necessary to convert the objects, the best practice is to do this conversion in a ToSoftwareName file that extends the public static class Convert. See the GSA_Toolkit for an example of this.

+

If API calls are required for the conversion, it's best to include the conversion process directly in the Create method. See Robot_Toolkit for an example of this.

+

In the Toolkit template, you will find some methods to get you started for creating BH.oM.Structure.Element.Bar objects.

+

AssignNextFreeId

+

This is a method for returning a free index that can be used in the creation process.

+

Important method to implement to get pushing of dependant properties working correctly. Some more info given in the Toolkit template.

+

Read

+

The read method is responsible for reading the external model and returning all objects that respect some rule (or, simply, all of them).

+

There are many available overloads for the Read. You should assume that any of them can be called "when appropriate" by the Push and Pull adapter actions.

+

The Read method in practice

+

The Read method scope should in general be specular to the Create: +- Use the external software API to import the objects. +- Call some conversion from the object model of the specific software to the BHoM object model.

+

Like for the Create, if no API calls are necessary to convert the objects, the best practice is to do this conversion in a FromSoftwareName file that extends the public static class Convert. See the GSA_Toolkit for an example of this.

+

Otherwise, if API calls are required for the conversion, it's best to include the conversion process directly in the Read method. See Robot_Toolkit for an example of this.

+

Update

+

The Update has to take care of copying properties from from a new version of an object (typically, the one currently being Pushed) to an old version of an object (typically, the one that has been Read from the external model).

+

The update will be called when appropriate by the Push.

+

The Update method in practice

+

If you have implemented your custom object Comparers and Dependency objects, then the CRUD method Update will be called for any objects deemed to already exist in the model.

+

Unlike the Create, Delete and Read, this method already exposes a simple implementation in the base Adapter, which may be enough for your purposes: it calls Delete and then Create.

+

This is not exactly what Update should be – it should really be an "edit" without deletion, actually – but this base implementation can be useful in the first stages of a Toolkit development.

+

This base implementation can always be overridden at the Toolkit level for a more appropriate one instead.

+

Delete

+

The Update has to take care of deleting an object from an external model. +The Delete is called by these Adapter Actions: the Remove and the Push. See the Adapter Actions page for more info.

+

The Delete method in practice

+

Deletion of objects with tag

+

By default, an object with multiple tags on it will not be deleted; it only will get that tag removed from itself.

+

This guaranties that elements created by other people/teams will not be damaged by your delete.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Adapter/index.html b/BHoM_Adapter/index.html new file mode 100644 index 000000000..599b2c006 --- /dev/null +++ b/BHoM_Adapter/index.html @@ -0,0 +1,4682 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Introduction to the BHoM_Adapter - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Introduction to the BHoM_Adapter

+

In this page you will find a first overview about what is the BHoM Adapter.

+
+

Note

+

▶️ Part of a series of pages. Next read: The Adapter Actions.

+

Before reading this page, have a look at the following pages:

+ +

and make sure you have a general understanding of:

+ +
+

What is a BHoM Adapter?

+

As shown in the Structure of the BHoM framework, an adapter is the part of BHoM responsible to convert and send-receive data (import/export) with external software (e.g. Robot, Revit, etc.).

+

In brief:

+
    +
  • An Adapter connects the BHoM to an external software.
  • +
  • Every Adapter offers different functionality, which we call Adapter Actions (for example, Push, which means exporting from BHoM, and Pull, which means importing to BHoM).
  • +
  • Different Adapters exist, one per each Software (e.g. Robot), or format (e.g. XML), to which BHoM can be converted.
  • +
+

Create an Adapter component or formula

+

Depending on the UI Software you are using, you can create an Adapter component (in Grasshopper, or formula if you are in Excel) like this:

+
+

Adapter component

+
+
+
+
    +
  1. Select the Adapter component:
    +image
  2. +
  3. Right click on centre of the component, then select an an Adapter from the menu (or use the text box to search):
    +image
  4. +
  5. A component gets created. See the inputs and follow the instructions of your chosen adapter to use it.
    +image
  6. +
+
+
+
    +
  1. Select an adapter from the menu:
    +image
  2. +
  3. A formula gets created in the active cell. See the inputs and follow the instructions of your chosen adapter to use it.
    +image
  4. +
+
+
+
+
+

Adapter actions

+

The Adapter Actions are the way to communicate with an external software via an Adapter.

+

Adapter Actions are BHoM components that you connect to a specific Adapter (e.g. Robot Adapter). Like any other BHoM component, are always look the same no matter what User Interface program you are using (Grasshopper, Excel, Dynamo...). In Grasshopper, there will be a component representing each action; in Dynamo, a node for each of them; in Excel, a formula will let you use them. You can find the Adapter Actions in the Adapter subcategory:

+
+

Adapter Actions

+
+
+
+
    +
  1. +

    Select an Actions from the "Adapter" category, e.g. Push: +image

    +
  2. +
  3. +

    The selected action is instantiated as a component to which an adapter can be connected. You will need to specify also the objects and possibly other inputs; keep reading. +image

    +
  4. +
+
+
+
    +
  1. +

    Select an Actions from the "Adapter" category, e.g. Push: +image

    +
  2. +
  3. +

    The selected action is instantiated as a formula to which an adapter can be connected. You will need to specify also the objects and possibly other inputs; keep reading. +image

    +
  4. +
+
+
+
+
+

Example usage: use Robot Adapter to Push (export) a BHoM model to Robot

+

Before looking at the Adapter Actions in more detail, see the following illustrative example of a Push to Robot.

+
+

Note

+

Although the Adapter actions always look the same, remember that each adapter may behave differently. Some adapters expect that you will use the Push with specific BHoM objects. For example, you can not push Architectural Rooms objects (BH.oM.Architecture.Room) to a Structural Adapter like RobotAdapter.

+
+
+

Illustrative example of Push

+
+
+
+

Example file download: Example push GH.zip

+

image

+
+
+

Example file download: Example push Excel.zip

+

image

+
+
+
+
+

Adapter actions overview

+

The following is a brief overview, more than enough for any user.
+A more in-detail explanation, for developers and/or curious users, is left in the next page of this wiki.

+

The first thing to understand is that the Adapter Actions do different things depending on the software they are targeting.
+In fact, the first input to any Adapter Action is always an Adapter, which targets a specific external software or platform. The first input Adapter is common to all Actions.

+

The last input to any Adapter action is an active Boolean, that can be True or False. If you insert the value True, the Action will be activated and it will do its thing. False, and it will sit comfortably not doing anything.

+

Push and Pull

+

The most commonly used actions are the Push and the Pull. You can think of Push and Pull as Export and Import: they are your "portal" towards external software.
+Again, taking Grasshopper UI as an example, they look like this (but they always have the same inputs and outputs, even if you are using Excel or Dynamo): +image

+

Push

+

The Push takes the input objects and: + - if they don't exist in the external model yet, they are created brand new; + - if they exist in the external model, they will be updated (edited); + - under some particular circumstances and for specific software, if some objects in the external software are deemed to be "old", the Push will delete those.

+

This method functionality varies widely depending on the software we are targeting. For example, it could do a thing as simple as simply writing a text representation of the input objects (like in the case of the File_Adapter) to taking care of object deletion and update (GSA_Adapter).

+

In the most complete case, the Push takes care of many different things when activated: ID assignment, avoiding object duplication, distinguishing which object needs updating versus to be exported brand new, etc.

+

Pull

+

The Pull simply grabs all the objects in the external model that satisfy the specified request (which simply is a query).

+

If no request is specified, depending on the attached adapter, it might be that all the objects of the connected model will be input, or simply nothing will be pulled. You can read more about the requests in the Adapter Actions - advanced parameters section.

+

Now, let's see the remaining "more advanced" Adapter Actions.

+

Move, Remove and Execute

+

Slightly more advanced Actions. Again taking Grasshopper as our UI of choice, they look like this: +image

+

Let's see what they do:

+
    +
  • +

    Move: This will copy objects over from a source connected software to another target software. It basically does a Pull and then a Push, without flooding the UI memory with the model you are transferring (which would happen if you were to manually Pull the objects, and then input them into a Push – between the two actions, they would have to be stored in the UI).

    +
  • +
  • +

    Remove: This will delete all the objects that match a specific request (essentially, a query). You can read more about the requests in the Adapter Actions - advanced parameters section.

    +
  • +
  • +

    Execute: This is used to ask the external software to execute a specific command such as Run analysis, for example. Different adapters have different compatible commands: try searching the CTRL+SHIFT+B menu for "[yourSoftwareName] Command" to see if there is any available one.

    +
  • +
+

Adapter Actions advanced parameters

+

You might have noticed that the Adapter Actions take some other particular input parameters that need to be explained: the Requests, the ActionConfig, and the Tags.

+

Their understanding is not essential to grasp the overall mechanics; however you can find their explanation in the Adapter Actions - Advanced parameters section of the wiki.

+

Wrap-up

+

The Adapter Actions have been designed using particular criteria that are explained in the next Wiki pages.

+

Most users might be satisfied with knowing that they have been developed like this so they can cover all possible use cases, while retaining ease of use.

+

Try some of the Samples and you should be good to go! 🚀

+

If you are a developer 🤖

+

The BHoM_Adapter is one of the base repositories, with one main Project called BHoM_Adapter.
+That one is the base BHoM_Adapter. The base BHoM_Adapter includes a series of methods that are common to all software connections. Specific Adapter implementations are included in what we call the Toolkits. The base BHoM_Adapter is an abstract class that is implemented in each Toolkit's Adapter implementation. A Toolkit's Adapter extends the base BHoM_Adapter.

+

We will see how to create a Toolkit later; however consider that, in general, a Toolkit is simply 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.

+

When you want to contribute to the BHoM and create a new software connection, you will not need to implement the Adapter Actions, at least in most of the cases.
+If you need to, however, you can override them (more details on that in last page of this Wiki, where we explain how to implement an Adapter in a new BHoM Toolkit).

+

So what is it that you need to implement?

+

The answer is: the so called CRUD Methods. We will see them in the next page.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Engine/BHoM_Engine-Classes/index.html b/BHoM_Engine/BHoM_Engine-Classes/index.html new file mode 100644 index 000000000..d989bc936 --- /dev/null +++ b/BHoM_Engine/BHoM_Engine-Classes/index.html @@ -0,0 +1,4480 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM Engine Classes - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM Engine Classes

+ + +

Class titles and notation

+

BHoM_Engine methods are always included into a static class.

+

Different static classes define specific scopes for the methods they contain. There are 5 different static classes: +- Create - instantiate new objects +- Modify - modify existing objects +- Query - get properties from existing objects +- Compute - perform calculation given an existing object and/or some parameters +- Convert - transform an existing object into a different type +- External - reflects methods from external libraries

+

Create

+
    +
  • Returns a new object of the given class.
  • +
  • Method is the name of the class being created.
  • +
+

Bar bar = Create.Bar(line);

+

Therefore the definition of the BHoMObject in the BHoM.dll should not contain any constructors (not even an empty default). +With the exception of objects that implement IImmutable. See explanation of explicitly immutable BHoM Objects somewhere else. Later.

+

Object Initialiser syntax can be used with BHoM.dll only +e.g.

+

Circle circ = new Circle { Centre = new Point { X = 10 } };

+

Grid grd = new Grid { Curves = new List<ICurve> { circ } };

+

Modify

+
    +
  • Returns a new version of the same object type as modified.
  • +
  • Although immutability is enforced throughout - this namespace is for any method that would be destructive for the object being operated on.
  • +
  • Simply use the Verb or SetNoun
  • +
+

.Rotate .Translate .MergeVertices .SetPropertyValue .Explode .SplitAt

+

Modify is not actually the correct term/tense now as we are immutable! But immutability is intrinsic in the strategy for the whole BHoM now so in the interest of clarity at both code and UI level Modify as a term is being used. Answers on a postcard for a better word!

+

Query

+
    +
  • Returns a derived property or objects or a simple boolean query (without modifying the information)
  • +
  • Although immutability is enforced throughout - this namespace is for any method that would NOT be destructive for the object being operated on.
  • +
  • Simply use the Noun, or Verb or prefix with Is
  • +
+

.Area .Mass .Distance .DotProduct

+

.Clone Could be interpreted as noun or verb, so works.

+

.Intersect

+

.IsPlanar .IsEqual .IsValid .IsClosed

+

In the case of explicitly immutable BHoM objects (see IImmutable), using this notation for derived properties will match notation of Readonly Properties also, which is neat.

+

Compute

+
    +
  • For computationally more intensive methods, iterative processes and/or solvers etc.
  • +
+

.EquilibriumPosition +.TextFromSpeech +.Integrate

+
    +
  • Or for modifying methods that would be destructive for the object being operated on but returns a different return type, or count of objects in a List.
  • +
+

.Split

+

There will potentially be grey areas between methods being classed as Query or Compute, however in general it should be clear using the above guidelines and the distinction is important to ensure code is easily discoverable from both as an end user.

+

Convert

+
    +
  • Returns a new type of object.
  • +
  • Method has the prefix of To or From
  • +
+

.ToJson() +.ToSVGString()

+

All convert methods must therefore be in a Convert Namespace within an _Engine project, thus separating this simple functionally from the _Adaptor project, in any software toolkits also.

+

External

+
    +
  • Contains a Constructors method, which returns a List<ConstructorInfo> that will be automatically reflected
  • +
  • Contains a Methods method, which returns a List<MethodInfo> that will be automatically reflected
  • +
  • Can contain any other method within the constraints presented below.
  • +
+

For methods whose signature or return type includes one or more schemas that are not sourced from either the BH.oM or the System namespaces.

+

Exceptions

+

Keep GetGeometry and SetGeometery as method names - these perhaps to be still treated slightly differently through new IGeometrical interface? Discuss.

+

Also allow an additional Objects Namespace where Engine code requires local class definitions for which there are good reasons to not promote to an _oM

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Engine/Humans_Engine/ViewQuality/index.html b/BHoM_Engine/Humans_Engine/ViewQuality/index.html new file mode 100644 index 000000000..f73d58327 --- /dev/null +++ b/BHoM_Engine/Humans_Engine/ViewQuality/index.html @@ -0,0 +1,4441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + View Quality Conventions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

View Quality Conventions

+

This page describes the view quality conventions that are used within the BHoM. +The description is intended to be a non-technical guide and provide universal access to understanding the methods of calculation of different view quality metrics. Links to the relevant methods are provided for those who wish to view the C# implementation.

+

Jump to the section of interest: +* Measure Cvalues + * Find focal points +* Measure Avalues + * Measure Occlusion +* Measure Evalues +* Background Information

+

Measure Cvalues

+

Method in Humans_Engine +cvalue calculation

+

Find focal points

+

Method in Humans_Engine +focalpoint

+

Measure Avalues

+

Method in Humans_Engine

+

Avalue is the percentage of the spectator's view cone filled with the playing area. +overview

+

p1v2

+

Measure Occlusion

+

Occlusion is the percentage of the spectator's view occluded by the heads of spectators in front. +occlusion +occlusion

+

Measure Evalues

+

Method in Humans_Engine

+

Description coming soon...

+

Background Information

+

References

+

Hudson and Westlake. Simulating human visual experience in stadiums. Proceedings of the Symposium on Simulation for Architecture & Urban Design. Society for Computer Simulation International, (2015).

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_Engine/index.html b/BHoM_Engine/index.html new file mode 100644 index 000000000..39fd2cce7 --- /dev/null +++ b/BHoM_Engine/index.html @@ -0,0 +1,4798 @@ + + + + + + + + + + + + + + + + + + + + + + + + + What is the BHoM Engine? - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

What is the BHoM Engine?

+

The BHoM Engine repository contains all the functions and algorithms that process BHoM objects.

+

As we saw in the introduction to the Object Model, this structure gives us a few advantages, in particular:

+
    +
  • we can see the BHoM object as a list of properties and their default values;
  • +
  • in the same way, the BHoM Engine can be seen as a big collection of functions.
  • +
+

Repo Structure

+

The BH.Engine repository is structured to reflect this strategy. The Visual Studio Solution contains several different Projects:

+

img

+

img

+

Each of those projects takes care of a different type of functionality. The "main" project however is the BHoM_Engine project: this contains everything that allows 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.

+
+

Why so many projects?

+

The main reason why the BHoM Engine is split in so many projects is to allow for a large number of people to be able to work simultaneously on different parts of the code.
+Keep in mind that every time a file is added, deleted or even moved, this changes the project file itself. Consequentially, submitting code to GitHub can become really painful when multiple people have modified the same files.
+Splitting code per project therefore limits the need to coordinate changes to the level of each focus group.

+
+

Another benefit will be visible when we get to the "Toolkit" level: having different project makes it easier to manage Namespaces and make certain functionalities "extendable" in other parts of the code, such as in Toolkits.

+

Folder structure

+

If we look inside each Engine project, we can see that there are some folders. Those folders help categorize the code into specific actions.

+

There are five possible action types that correspond to five different folder names: Compute, Convert, Create, Modify, and Query.

+

Let's consider the Geometry_Engine project; we can see that it contains all of those folders:

+

img

+

Those five action names should be the same in all projects; however it's not mandatory that an Engine project should have all of them.

+

Each folder contains C# files; those files must be named as the target of this action.

+

Engine method types

+

In order to sort methods and organise them, 5 different categories of Engine methods exist. All methods will fall into one of these categories.

+
    +
  • Create: methods that instantiate a new object. Remember that the Objects are simple classes defined with no constructor (unless they must be IImmutable -- the only exception where constructors are allowed). You can define any number of methods that create the same objects via any combination of input parameters.
  • +
  • Modify: methods that modify an object. Generally, the modify method should have a return type that is of the same type of its first argument. This is to state that the method effectively returns a modified copy of the input object.
  • +
  • Query: methods that return some derived value from the input object. A derived value is something that is not found among the defining properties of the object, but that can be inferred from them. For example, the length of a Line object, if the Line itself is defined only by its start and end point.
  • +
  • Convert: methods that transform the input object into another type that has similar, or equivalent, meaning. For example, converting a BHoM Structural Bar into a Robot Bar.
  • +
  • Compute: methods that perform some computational or I/O heavy functionality, or which do not fall into any other of the previous categories.
  • +
+

If you are in doubt, try finding another file that does a similar thing in another project, and see where that is placed.

+

For example, in the Geometry_Engine project there is a Query folder that contains, among others, a Length.cs file. This file contains methods that take care of Querying the Length for geometric objects. Consider that another equally named Length.cs file might be present in the Query folder of other Engine projects; this is the case, for example, of the Structure_Engine project, where the file contains method to compute the link of Bars (structural objects).

+

File Structure

+

The file is structured in a slightly unusual way for people used to classic object-oriented programming, so let's look at an example. The following is an extract from the ClosestPoint.cs file of the Geometry_Engine project.

+
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) {...}
+
+        /***************************************************/
+
+        ...
+    }
+}
+
+

A few things should be noted:

+
    +
  • +

    The Namespace always starts with BH.Engine followed by the project name (without the suffix "__Engine_", obviously).

    +
  • +
  • +

    The file should contain one and only one class, named like the containing folder. For example, any C# file contained in the "Query" folder will contain only one class called Query.

    +
  • +
  • +

    Consequently, the name of the file itself will not correspond to the name of the class, as it is usually recommended in Object Oriented Programming. The file name will generally only reflect the name of the methods defined in it.

    +
  • +
  • +

    Note that the class is declared as a partial class. Also note that the class is declared as static.

    +
  • +
+
+

Static and partial

+

The last point might be a bit cryptic for those that are not fluent in C#. Here is a brief explanation that should be enough to move on the next topics.

+

static means that the content of the class is available without the need to create (instantiate) an object of that class. However, that requires that all the functions contained in the class are declared static as well.

+

On the other hand, partial means that the full content of that class can be spread between multiple files.

+

Having the engine action classes declared as static and partial helps us simplifying the structure of the code and expose only the relevant bits to the average contributors.

+
+

Class Structure

+

Fluent C# users should have no problem understanding the structure of Engine classes.

+

For those that want to get stuck without too many technical details, here are a few instructions on how to edit the action classes.

+
    +
  • Inside the 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 the dot . notation. For example, if you have an instance of an Arc type called myArc, you will be able to do myArc.ClosestPoint(refPoint). This way of defining functions is called Extension Methods and will be better explained below.
  • +
  • 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 {...}
+    }
+}
+
+
+

Advanced topics

+

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 below are a bit more advanced; if you follow them, however, you will be able to provide a better experience to those using your code. Knowing what Polymorphism is and what the C# dynamic type is will also likely get you out of problematic situations, especially 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 dynamic type, 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.

+

Fallback Methods

+

But what if we do not have a method implemented for every type that that can be dynamically called by IBounds? That is what private fallback methods are for. In general fallback methods are used for handling unexpected behaviours of main method. In this case it should log an error with a proper message (see Handling Exceptional Events for more information) and return null or NaN.

+
namespace BH.Engine.Geometry
+{
+    public static partial class Query
+    {
+        ...
+
+        /***************************************************/
+        /**** Private Methods - Fallback                ****/
+        /***************************************************/
+
+        private static BoundingBox Bounds(IGeometry geometry)
+        {
+            Reflection.Compute.RecordError($"Bounds is not implemented for IGeometry of type: {geometry.GetType().Name}.");
+            return null;
+        }
+
+        /***************************************************/
+
+        ...
+    }
+}
+
+

Being private and having an interface as the input prevents it from being accidentally called. It will be triggerd only if IBounds() couldn't find a proper method for the input type.

+

Additional comment: +- At this moment BHoM does not handle nullable booleans. This means it is impossible to return null from a bool method. In such cases fallback methods can throw [NotImplementedException].

+

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.

+

img

+

RunExtensionMethod Pattern

+

The concept of polymorphic extension methods explained above has one serious limitation: it works only if all methods aimed to be called by the dynamically cast object are contained within one class. That is not the case e.g. for Geometry method, which is divided into a series of Query classes spread across discipline-specific namespaces: BH.Engine.Structure, BH.Engine.Geometry etc. To enable IGeometry method, a special pattern based on RunExtensionMethod needs to be applied:

+
namespace BH.Engine.Spatial
+{
+    public static partial class Query
+    {
+        /******************************************/
+        /****            IElement0D            ****/
+        /******************************************/
+
+        [Description("Queries the defining geometrical object which all spatial operations will act on.")]
+        [Input("element0D", "The IElement0D to get the defining geometry from.")]
+        [Output("point", "The IElement0Ds base geometrical point object.")]
+        public static Point IGeometry(this IElement0D element0D)
+        {
+            return Reflection.Compute.RunExtensionMethod(element0D, "Geometry") as Point;
+        }
+
+        /******************************************/
+    }
+}
+
+

RunExtensionMethod method is a Reflection-based mechanism that runs the extension method relevant to type of the argument, regardless the class in which that actual method is implemented. In the case above, IGeometry method belongs to BH.Engine.Spatial.Query class, while e.g. the method for BH.oM.Geometry.Point (which implements IElement0D interface) would be in BH.Engine.Geometry.Query - thanks to calling RunExtensionMethod instead of dynamic casting it can be called successfully. The next code snippet shows the same mechanism for methods with more than one input argument (in this case being an IElement0D to be modified and a Point to overwrite the geometry of the former).

+
namespace BH.Engine.Spatial
+{
+    public static partial class Modify
+    {
+        /******************************************/
+        /****            IElement0D            ****/
+        /******************************************/
+
+        [Description("Modifies the geometry of a IElement0D to be the provided point's. The IElement0Ds other properties are unaffected.")]
+        [Input("element0D", "The IElement0D to modify the geometry of.")]
+        [Input("point", "The new point geometry for the IElement0D.")]
+        [Output("element0D", "A IElement0D with the properties of 'element0D' and the location of 'point'.")]
+        public static IElement0D ISetGeometry(this IElement0D element0D, Point point)
+        {
+            return Reflection.Compute.RunExtensionMethod(element0D, "SetGeometry", new object[] { point }) as IElement0D;
+        }
+
+        /******************************************/
+    }
+}
+
+

Naturally, in order to enable the use of RunExtensionMethod pattern by a given type, a correctly named extension method taking argument of such type needs to be implemented.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_UI/index.html b/BHoM_UI/index.html new file mode 100644 index 000000000..0dbd794eb --- /dev/null +++ b/BHoM_UI/index.html @@ -0,0 +1,4325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BH.UI: Expose your code to User Interfaces - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BH.UI: Expose your code to User Interfaces

+

For an user perspective on the UIs, you might be looking for Using the BHoM.

+

Supported UIs

+

The UI layer has been designed so that it will automatically pick everything implemented in the BHoM, the Engines and the Adapters without the need to change anything on the code of the UI.

+

Here's what the menu looks like in Grasshopper. The number of component there doesn't have to change when more functionality is added to the rest of the code:

+

img

+

When dropped on the cavas, most of those components will have no input and no output. They will be converted to their final form once you have selected what they need to be in their menu:

+

img

+

You can get more information on how to use one of the BHoM UI on this page.

+

Automatic rendering of a BHoMObject

+

BHoMObjects are rich objects, which may or may not contain a geometry representation. +If a geometry representation can be extracted, either from one of its properties, or as a result of their manipulation, it can be used to automatically render the object in the GUIs. The only action to enable that, is to create a Query.Geometry method, whose only parameter is the object you want to display, and place it in the Engine namespace that corresponds to the oM of the object. The method has to return an IGeometry or one of its assignable types.

+

For example, let's assume I want to automatically display a BH.oM.Structure.Elements.Bar. I'd do as follows: +1. Go into the correspondent Engine - i.e. BH.Engine.Structure +1. Go into the Query folder - i.e. BH.Engine.Structure.Query +1. If it does not exist yet, create a Geometry.cs file +1. Add an extension method name Geometry, whose only parameter is the object you want to display: +

public static Line Geometry(this BH.oM.Structure.Elements.Bar bar)
+{
+  // Extract your geometry
+  return calculatedGeoemtry
+}
+

+

Creating a new UI

+

Most of the functionality required by every UI has already been ported to the BHoM_UI repository or to the Engine (when used in more than the UIs). This makes the creation of a new UI a lot less cumbersome but this is still by no mean a small task. I would recommend to reach out to those that have already worked on UI (check the contributors of those repos) before you start writing a new UI from scratch.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/BHoM-Units-conventions/index.html b/BHoM_oM/BHoM-Units-conventions/index.html new file mode 100644 index 000000000..820163a4b --- /dev/null +++ b/BHoM_oM/BHoM-Units-conventions/index.html @@ -0,0 +1,4323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM Unit conventions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM Unit conventions

+

This page describes the Units conventions for the BHoM.

+

General philosophy: use SI!

+

The BHoM framework adheres as much as possible to the conventions of the SI system.

+

Any Engine method must operate in SI to avoid complexity of Unit Conversions inside calculations. +Conversion to and from SI is the responsibility of the Converts inside the Adapters.

+

When some units (derived or not) are not explicitly covered by this Wiki page, it is generally safe to assume that measures expressed in SI units will not be converted by the BHoM.

+
    +
  • Mass: kilograms [kg]
  • +
  • Length: meters [m]
  • +
  • Force: Newtons [N]
  • +
  • Moments: [N*m]
  • +
  • Stress/Pressure: [N/m²]
  • +
  • Spring constraints: [N/m]
  • +
  • Rotational constraints: [N*m/rad]
  • +
  • Temperature: [K]
  • +
+

Localisation toolkit

+

The Localisation_Toolkit provides support for conversion between SI and other units systems.

+

Quantity attributes

+

BHoM object properties can be decorated with a Quantity Attribute to define (in SI) what unity the property should be considered in. +This is to be applied only to properties that are of a primitive numerical type, e.g. int, double, etc.

+

See Quantities_oM for the available attributes.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/Base_oM/Interfaces/IImmutable/index.html b/BHoM_oM/Base_oM/Interfaces/IImmutable/index.html new file mode 100644 index 000000000..c4ebf882c --- /dev/null +++ b/BHoM_oM/Base_oM/Interfaces/IImmutable/index.html @@ -0,0 +1,4280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IImmutable - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IImmutable

+ +

The IImmutable interface makes an object unmodifiable after it was instantiated. In order to modify an IImmutable object, a new object with the desired properties needs to be instantiated, where all the properties that are required to stay the same should be copied from the old object.

+

IImmutable should be implemented:

+

a) if objects instantiated from a class should not be modifiable, by design, in some or all of its properties;
+b) if objects contain properties that are non-orthogonal.

+

Whilst reason (a) is self-explanatory, (b) is due to a specific problem that non-orthogonal properties expose.

+

As a reminder, a class with ortogonal properties is a class whose properties all contain information that cannot be derived from other properties. Orthogonality is a software design principle for writing components in a way that changing one component doesn’t affect other components. For example, an orthogonal "Column" class may define a Start Point and an End Point as separate properties, but then it cannot define a third property called “Line” which goes between a start point and an end point, as it would be redundant: modifying the start or end point would require to modify the Line property too. For this reason, class with non-orthogonal properties should implement the IImmutable interface, because the consistency of its properties can be guaranteed only when the class is instantiated.

+

How to implement it

+

To implement the IImmutable interface, you need to make two actions:

+
    +
  1. Inherit from it: i.e. public class YourObject : IImmutable
  2. +
  3. The properties you want to be immutable must be public, get only, and contain a default value. i.e. public string Title { get; } = ""
  4. +
  5. All the properties that are not immutable, can follow the usual BHoM conventions, public, get and set, and have a default value
  6. +
  7. It must implement only one constructor, whose parameters are types of all the immutable properties of the object.
  8. +
+

For an example, you can check the BH.oM.Structure.SectionProperties.SteelSection from the Structure_oM: +Steel Section example

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/Dimensional_oM/IElement-required-extension-methods/index.html b/BHoM_oM/Dimensional_oM/IElement-required-extension-methods/index.html new file mode 100644 index 000000000..5ee086efb --- /dev/null +++ b/BHoM_oM/Dimensional_oM/IElement-required-extension-methods/index.html @@ -0,0 +1,4324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Extension methods required for the IElement interface - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Extension methods required for the IElement interface

+

The following points outlines the use of the dimensional interfaces as well as extension methods required to be implemented by them for them to function correctly in the Spatial_Engine methods.

+

Please note that for classes that implement any of the following analytical interfaces, an default implementation already exists in the Analytical_Engine and for those classes an implementation is only needed if any extra action needs to be taken for that particular case. The analytical interfaces with default support are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Analytical InterfaceDimensional interface implemented
INodeIElement0D
ILink<TNode>IElement1D
IEdgeIElement1D
IOpening<TEdge>IElement2D
IPanel<TEdge, TOpening>IElement2D
+

Please note that the default implementations do not cover the mass interface IElementM.

+
    +
  1. +

    If the BHoM class implements an IElement interface corresponding with its geometrical representation:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    InterfaceImplementing classes
    IElement0DClasses which can be represented by Point (e.g. nodes)
    IElement1DClasses which can be represented by ICurve (e.g. bars)
    IElement2DClasses which can be represented by a planar set of
    closed ICurves (e.g. planar building panels)
    IElementMClasses which is containing matter in the form of a material and a volume
    +
  2. +
  3. +

    It needs to have the following methods implemented in it's oM-specific Engine:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    InterfaceRequired methodsOptional methodsWhen
    IElement0D
    • Geometry()
    • SetGeometry(Point point)
    • HasMergeablePropertiesWith(IElement0D)

    IElement1D
    • Geometry()
    • SetGeometry(ICurve curve)
    • HasMergeablePropertiesWith(IElement1D)
    • Elements0D()
    • SetElements0D(
      List<IElement0D> newElements0D)
    • NewElement0D(Point point)
    IElement1D which endpoints are defined by IElement0D
    IElement2D
    • OutlineElements1D()
    • SetOutlineElements1D(
      List<IElement1D> outlineElements1D)
    • NewElement1D(ICurve curve)
    • HasMergeablePropertiesWith(IElement2D)
    • InternalElements2D()
    • NewInternalElement2D()
    • SetInternalElements2D(
      List<IElement2D> internalElements2D)
    If the IElement2D has internal elements
    IElementM
    • MaterialComposition()
    • SolidVolume()
    or
    • VolumetricMaterialTakeoff()
    +
  4. +
  5. +

    Spatial_Engine contains a default Transform method for all IElementXDs. This implementation only covers the transformation of the base geometry, and does not handle any additional parameters, such as local orientations of the element. For an object that contains this additional layer of information, a object specific Transform method must be implemented. +

    +
  6. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/Geometry_oM/index.html b/BHoM_oM/Geometry_oM/index.html new file mode 100644 index 000000000..4efb7eb76 --- /dev/null +++ b/BHoM_oM/Geometry_oM/index.html @@ -0,0 +1,4422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Geometry oM - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Geometry oM

+

Geometry_oM is the core library, on which all engineering BHoM objects are based. It provides a common foundation that allows to store and represent spatial information about any type of object in any scale: building elements, their properties and others, both physical and abstract.

+

All objects can be found here in the Geometry_oM

+

The code is divided into a few thematic domains, each stored in a separate folder: +- Coordinate System +- Curve +- Interface +- Math +- Mesh +- Misc +- SettingOut +- ShapeProfiles +- Solid +- Surface +- Vector

+

All classes belong to one namespace (BH.oM.Geometry) with one exception of Coordinate Systems, which live under BH.oM.Geometry.CoordinateSystem. +All methods referring to the geometry belong to BH.Engine.Geometry namespace.

+

Interfaces

+

Two separate families of interfaces coexist in Geometry_oM. First of them organizes the classes within the namespace:

+ + + + + + + + + + + + + + + + + + + + + +
InterfaceImplementing classes
IGeometryAll classes within the namespace
ICurveCurve classes
ISurfaceSurface classes
+

The other extends the applicability of the geometry-related methods to all objects, which spatial characteristics are represented by a certain geometry type:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
InterfaceImplementing classes
IElement0DAll classes represented by Point
IElement1DAll classes represented by ICurve
IElement2DAll classes represented by a planar set of closed ICurves (e.g. building panels)
IElement3DAll classes represented by a closed volume (e.g. room spaces) - not implemented yet
+

Tolerances

+

There is a range of constants representing default tolerances depending on the tolerance type and scale of the model:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ScaleValue
Micro1e-9
Meso1e-6
Macro1e-3
Angle1e-6
+

Conversion to proprietary software packages

+

While being pulled/pushed through the Adapters, the BHoM geometry is converted to relevant geometry format used by each software package.

+

BHoM Rhinoceros conversion table

+

Known issues

+

At the current stage, Geometry_oM bears a few limitations: +- Nurbs are not supported (although there is a framework for them in place) +- 3-dimensional objects (curved surfaces, volumes etc.) are not supported with a few exceptions +- Boolean operations on regions contain a few bugs

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/Structure_oM/BHoM-Structural-Conventions/index.html b/BHoM_oM/Structure_oM/BHoM-Structural-Conventions/index.html new file mode 100644 index 000000000..7a2bebe28 --- /dev/null +++ b/BHoM_oM/Structure_oM/BHoM-Structural-Conventions/index.html @@ -0,0 +1,4501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Structural and geometrical conventions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Structural and geometrical conventions

+

This page covers Structural and Geometrical conventions for the BHoM framework.

+

1D-elements

+

Coordinate system

+

The following local coordinate system is adopted for 1D-elements e.g. beams, columns etc:

+

+
    +
  • x-axis along the centre line of the element from start to end
  • +
  • z-axis as the normal direction of the element
  • +
  • y-axis transverse to the normal
  • +
+

Linear elements

+

For non-vertical members the local z is aligned with the global z and rotated with the orientation angle around the local x.

+

For vertical members the local y is aligned with the global y and rotated with the orientation angle around the local x.

+

A bar is vertical if its projected length to the horizontal plane is less than 0.0001, i.e. a tolerance of 0.1mm on verticality.

+

Curved planar elements

+

For curved elements the local z is aligned with the normal of the plane that the curve fits in and rotated around the curve axis with the orientation angle.

+

Section property nomenclature

+

Area - Area of the section property
+Iy - Second moment of area, major axis
+Iz - Second moment of area, minor axis
+Wel,y - Elastic bending capacity, major axis
+Wel,z - Elastic bending capacity, minor axis
+Wpl,y - Plastic bending capacity, major axis
+Wpl,z - Plastic bending capacity, minor axis
+Rg,y - Radius of gyration, major axis
+Rg,z - Radius of gyration, minor axis

+

Vz - Distance centre to top fibre
+Vp,z - Distance centre to bottom fibre
+Vy - Distance centre to rightmost fibre
+Vp,y - Distance centre to leftmost fibre
+As,z - Shear area, major axis
+As,y - Shear area, minor axis

+

Signs of section forces

+

The directions for the section forces in a cut of a beam can be seen in the image below:

+

+

This is: +* Normal force positive along the local x-axis +* Shear forces positive along the local y and z-axes +* Bending moments positive around the local axis by using the right hand rule

+

This leads to the following:

+

Axial force Fx

+

Positive (+) = Tension
+Negative (-) = Compression

+

Major axis bending moment My and shear force Fz

+

As shown in the following diagram.

+

+

Minor axis bending moment Mz and shear force Fy

+

Same sign convention as for major axis.

+

Torsional moment Mx

+

The torsional moment follows the Right-hand rule convention.

+

+ + +

Bar offsets

+

Bar offsets specify a local vector from the bars node to where the bar is calculated from, with a rigid link between the Node object and the analysis bars end point.

+

Hence: +* a BHoM bars nodes are where it attaches to other nodes, +* offsets are specified in the local coordinate system and is a translation from the node, +* local x = bar.Tangent(); +* local z = bar.Normal(); +* node + offset is where the bar node is analytically +* the space between is a rigid link

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/Structure_oM/Shear-Area-Derivation/index.html b/BHoM_oM/Structure_oM/Shear-Area-Derivation/index.html new file mode 100644 index 000000000..a8b5e61db --- /dev/null +++ b/BHoM_oM/Structure_oM/Shear-Area-Derivation/index.html @@ -0,0 +1,4332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Shear Area Derivation - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Shear Area Derivation

+ +

Shear Area Derivation

+

It is here outlined how BHoM calculates shear area for a section
+Shear Area formula used for calculation:
+
+And A(x) is defined as all the points less than x within the region A.

+

Moment of inertia is know and hence the denominator will be the focus.
+

+

Sy for an area can be calculated for a region by its bounding curves with Greens Therom:
+
+which for line segments is:
+

+

And while calculating this for the entire region as line segments is easy, we want to have the regions size as a variable of x.
+So we make some assumptions about the region we are evaluating. +* Its upper edge is always on the X-axis +* No overhangs

+

i.e. its thickness at any x is defined by its lower edge,
+achieved by using WetBlanketIntegration()
+Example:
+

+

We will then calculate the solution for each line segment from left to right.
+This is important as Sy is dependent on everything to the left of it.

+

We then split the solution for Sy into three parts: +* S0, The partial solution for every previous line, i.e. sum until current +* The current line segment with variable t +* A closing line segment with variable t, connects the end of the current line segment to the X-axis

+

Closing along the X-axis is not needed as the horizontal solution is always zero.
+Visual representation of the area it works on:
+

+

We will now want to define all variables in relation to t
+

+

And then plug everything into the integral
+

+

To summarise the practical proccess

+
    +
  1. The region will be converted to the right format by WetBlanketInterpetation()
  2. +
  3. The integral is evaluated for the first line segment and added to the results sum
  4. +
  5. The S0 value for the first line segment is calculated and added to the S_0 sum
  6. +
  7. step 2 and 3 repeats for every line except the last
  8. +
  9. The squared Moment of inertia is then divided by the result
  10. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BHoM_oM/index.html b/BHoM_oM/index.html new file mode 100644 index 000000000..55005ef48 --- /dev/null +++ b/BHoM_oM/index.html @@ -0,0 +1,4598 @@ + + + + + + + + + + + + + + + + + + + + + + + + + The Object Model (oM): define new objects - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

The Object Model (oM): define new objects

+

This section introduces the BHoMObject, which is the foundational class for most of the Objects found in BHoM.

+

We also introduce the IObject, the base interface for everything in BHoM.

+

BHoMObject Code Structure and Content

+

A typical BHoM object definition is given simply by defining a class with some public properties. That's it! No constructors or anything needed here.

+

Here is an example of what a BHoM object definition looks like:

+
using BH.oM.Base;
+using BH.oM.Geometry;
+
+namespace BH.oM.Acoustic
+{
+    public class Speaker : BHoMObject
+    {
+        /***************************************************/
+        /**** Properties                                ****/
+        /***************************************************/
+
+        public Point Position { get; set; } = new Point();
+
+        public Vector Direction { get; set; } = new Vector();
+
+        public string Category { get; set; } = "";
+
+        /***************************************************/
+    }
+}
+
+

In general, most classes defined in BHoM are a BHoM object, except particular cases. +Among these exceptions, you can find Geometry and Result types. +The reason for this is both conceptual and to aid performance. Geometries and Results are not "objects" in the strict sense of the term. In addition, separating those types from actual BHoMObject objects greatly helps with performance down the line.

+

Inheritance from BHoMObject

+

Note that the name of a class in a new object definition is followed by : BHoMObject. This is to say that this object inherits from BHoMObject. This is important if you want your new class to benefit from the properties and functionalities a BHoM object provides.

+

Here is a part of the BHoMObject class definition:

+
namespace BH.oM.Base
+{
+    public class BHoMObject : IObject
+    {
+        /***************************************************/
+        /**** Properties                                ****/
+        /***************************************************/
+
+        public Guid BHoM_Guid { get; set; } = Guid.NewGuid();
+
+        public string Name { get; set; } = "";
+
+        public HashSet<string> Tags { get; set; } = new HashSet<string>();
+
+        public Dictionary<string, object> CustomData { get; set; } = new Dictionary<string, object>();
+
+   }
+}
+
+

As you can see, the BHoMObject only contains a set of properties.

+

As for any other class in the BHoM framework, we try to keep behaviour (functions, methods) and properties separated. Minor exceptions to this separation are seldom made for for practical efficiency and technical reasons. +The functionalities of the BHoMObject, as well as of the other BHoM framework types, are defined in the BHoM_Engine.

+

Everything is an IObject

+

As we said before, not everything is an BHoMObject: exceptions are Geometry and Results objects.

+

However, in order to easily identify all the types coming from the BHoM framework, a basic type, or interface, is needed.

+

That's why everything is defined to be an IObject at its root. All BHoM objects will always be an IObject, as BHoMObject is itself inheriting from IObject. Everything else will be too through the chain of interfaces.

+

Let's have a look at one of the Geometry objects, Pipe. As you can see, it inherits from ISurface, one of the base Geometry types.

+
namespace BH.oM.Geometry
+{
+    public class Pipe : ISurface
+    {
+        /***************************************************/
+        /**** Properties                                ****/
+        /***************************************************/
+
+        public ICurve Centreline { get; set; } = new Line();
+
+        public double Radius { get; set; } = 0;
+
+        public bool Capped { get; set; } = true;
+
+        /***************************************************/
+    }
+}
+
+

The interface ISurface inherits from another interface, IGeometry:

+

namespace BH.oM.Geometry
+{
+    public interface ISurface : IGeometry {}
+}
+
+And finally, IGeometry inherits from IObject, which as we said will always be the top-level of any type defined in the BHoM framework:

+
namespace BH.oM.Geometry
+{
+    public interface IGeometry : IObject {}
+}
+
+

Defining Properties

+

Properties correspond to the information you need to define your object (to the exception of the properties the BHoMObject class already provides). A few things to keep in mind when you create those:

+
    +
  • All properties must be public and have a public get and set methods, written {get; set;}. (This means that readonly properties are not directly allowed - see paragraph below "Immutable Objects" if you want to know more).
  • +
  • Make sure you provide a default value X for your properties by using = X; at the end of their definition; If a properties is too complex to be defined that way, simply set it to null (write = null; at the end).
  • +
+

As objects grow in complexity, it is useful to think in terms of splitting an object's properties into categories: +1. Object Defining properties. The minimal required information you need to construct the full object. +These should generally be the properties of the objects defined in the BHoM

+
    +
  1. +

    Derived properties. +Any property that could be calculated from the other properties. These should generally be handled by the BHoM_Engine using extension methods. This choice allows to calculate and obtain those properties only when needed; however, it also mean that you will have to write an explicit "get" method that users will be able to access through the dot . accessor.

    +
  2. +
  3. +

    Software specific properties such as Software IDs, etc. To ensure that the BHoM is software agnostic, we resorted to store this information in a dynamic (not statically typed) way. That's why we're using a Dictionary (list of key-value pairs) property of the BHoMObject called CustomData. +For example, the ID assigned to an object for a certain software will be stored as a value of the Key softwareName_id.

    +
  4. +
  5. +

    Results from analysis. These are to be generally stored as a completely different set of classes, as you can have thousands of results per object.

    +
  6. +
+

As an example between Defining and Derived properties for geometry: +A line is defined by two points. These two points are properties of the line (category 1). +A line can also have a length, but as that can be derived from the points, this instead sits in the BHoM_Engine as a method called "Length()" (category 2). +This structure makes sure that on update of the points, the length will also be updated ensuring compatibility of properties at all times.

+

Defining Constructors and Local Methods

+

Important: To the exception of Immutable Objects, BHoM objects should never have a constructor. In general, there should be no method defined in the class either (see Casting methods). So, ultimately, a BHoM object is really nothing more than a list of properties and their default values. Objects will be created either by using an Object Initialiser or via a Create method from the Engine.

+

Anything that manipulates data should generally be in the BHoM Engine. That being said, there are rare occasions where you will see a local method written directly in the object definition. Those methods are generally created there for optimisation reasons or because of the constraints of C# and are therefore the exception, not the rule.

+

For those of you coming from object oriented programming, it might seems quite unnatural to take functionality outside a class as much as possible. There is a few reasons why we have gone that direction:

+
    +
  • Properties of an object are unlikely to change frequently and it is reasonable to expect a list of properties to converge quickly to a final solution, never to be touched again. The methods, on the other hand are always growing, improving or being debugged. Keeping them in more isolated packages will reduce the impact of their change.
  • +
  • We want as many people to be able to contribute as possible. While not everyone will be able to write complex algorithms, we expect every engineer to be able to define what properties should be found in an object he/she is using regularly. By separating the complexity levels in different repos, people are enabled to participate by focusing on the part they are comfortable with.
  • +
  • Some of the contributors to the BHoM might wish to keep a few methods and algorithms related to a BHoM object private. By limiting the BHoM to object definitions, we are making it easier to share the object models without being forced to share anything else. Do not worry though, the Engine already contains plenty of useful methods and is constantly growing.
  • +
+

The main disadvantage is that the hierarchical structure of the repositories makes mandatory to update/rebuild any other repository that comes down. For example, any change to the BHoM repository means there is large ripple effect on nearly every other repository.

+

Namespace and Folder Structure

+
    +
  • BHoM objects are organised as shown in the image below. All analytical objects are stored in their respective discipline project (e.g. Structure, Environment,...).
  • +
  • A Common project is user for objects shared between disciplines.
  • +
  • The inter-disciplinary representation is expressed through physical objects (stored in the Physical folder).
  • +
  • Finally, the BHoM and Geometry folders contain core objects and geometry definitions respectively.
  • +
+
+

+

+
Example view of the BHoM solution
+
+

Namespaces have to match the folder structure.

+

In the rare case where folders are more than 3 levels deep, the namespaces are allowed to stop there. For example, the BH.oM.Structure.Results folder contains subfolders. Objects defined in those subfolders are allowed to use the namespace BH.oM.Structure.Results instead of BH.oM.Structure.Results.SubFolder.

+

+

Immutable Objects

+

Warning: This is more advanced feature and not necessary in 99% of the case so you can safely skip this.

+

For some rare objects, it would be problematic to keep only the Defining properties. That is generally the case if the Derived properties are very expensive to compute. In that case, those objects should inherit from the IImutable interface. This is explicitly stating that the properties of those objects should not be modified as it would create inconsistencies within the object. In that case, the properties that are overlapping would only have a { get; } accessor instead of the usual { get; set; }. Here's an example of such a class (with some skipped section highlighted as ...)

+
public class CableSection : BHoMObject, ISectionProperty, IImmutable
+{
+    /***************************************************/
+    /**** Properties                                ****/
+    /***************************************************/
+
+    public Material Material { get; set; } = null;
+
+    /***************************************************/
+    /**** Properties - Section dimensions           ****/
+    /***************************************************/
+
+    public int NumberOfCables { get; } = 0;
+
+    public double CableDiameter { get; } = 0;
+
+    public CableType CableType { get; } = CableType.FullLockedCoil;
+
+    public double BreakingLoad { get; }
+
+    ...
+
+    /***************************************************/
+    /**** Constructors                              ****/
+    /***************************************************/
+
+    public CableSection(...)
+    {
+        ...
+    }
+
+    /***************************************************/
+}
+
+

Apart from the use of { get; } instead of { get; set; }, you will notice that IImmutable objects will have to define their own constructors inside the class. This is because Object Initialiser do not work on properties without a set so we cannot simply define the constructors in the Engine as we usually do.

+

Casting Methods

+

Warning: This is more advanced feature and not necessary in 99% of the case so you can safely skip this.

+

It is convenient for some objects to be able to be casted from something else. For Example, a geometrical Point could be casted from a Vector or a structural Node could be casted from a Point. This is especially useful inside a user interface. Here's an example where this case is relevant:

+

img

+

public class Node : BHoMObject
+{
+    /***************************************************/
+    /**** Properties                                ****/
+    /***************************************************/
+
+    public Point Position { get; set; } = new Point();
+
+    public Constraint6DOF Constraint { get; set; } = null;
+
+
+    /***************************************************/
+    /**** Explicit Casting                          ****/
+    /***************************************************/
+
+    public static explicit operator Node(Point point)
+    {
+        return new Node { Position = point };
+    }
+
+    /***************************************************/
+}
+
+As you can see, we can skip the step of creating a Node since it would only need the Point anyway.

+

Unfortunately, C# doesn't allow to define this outside the class so we have no choice but to do it in the BHoM. Be mindful that this is only relevant when an object could be created from a single other element so this only apply in unique cases and shouldn't be defined in every class.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Basics/Coding fundamentals/Technical-philosophy-of-the-BHoM/index.html b/Basics/Coding fundamentals/Technical-philosophy-of-the-BHoM/index.html new file mode 100644 index 000000000..554a06d5d --- /dev/null +++ b/Basics/Coding fundamentals/Technical-philosophy-of-the-BHoM/index.html @@ -0,0 +1,4401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Technical philosophy of the BHoM - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Technical philosophy of the BHoM

+

The Buildings and Habitats object Model (BHoM) is designed to be compatible with both visual flow-based programming (e.g. Grasshopper, Excel, Dynamo) and with proper programming (e.g. coding in C#).

+

This is to integrate well in the workflow of any professional in the AEC industry, regardless of their level of computational proficiency: BHoM is a platform for combining the efforts of the professional programmer with those of any enthusiastic scripter/computational designer/engineer/architect, all in the same ecosystem.

+

The basic architecture

+

The Buildings and Habitats object Model is organised as four distinct categories of code: object Model, Engine, Adapter and User Interface.

+


+

1. The object model [oM] is nothing more than structured data - a collection of schemas.

+

The oM is defined as naturally type strong C# classes, but comprising of only simple public Get Set Properties, with all methods excluded from the class definition including even the requirement for default constructors.1
+Ultimately, they are very close to C type structures with the added benefit of inheritance and polymorphism that a C# class provides.

+


+

+

2. The Engine is nothing more than data manipulators - a structured collection of components/methods.

+

All functionality is provided to the base types through extension methods in the Engine and organised as static methods within public static partial classes. +Immutability is enforced on inputs of each method to enable translation to flow based programming environment.

+


+

+

3. A common protocol for adaptors enables a single interface irrespective of the external software dealing with.

+

IO and CRUD concepts are combined to enable convenient Push-Pull visual programming UI with CRUD functions interfacing with the external application.
+Crucially, the abstract BHoM_Adapter enables centralized handling of complex data merging so that creators of new adapters can focus on what makes their adapter different, reusing what is common and has already been solved

+


+

+

4. The UI exposes code directly. Same terminology. Complete transparency.

+

By leveraging dynamic binding – mostly leveraging C#'s Reflection – all objects, engine methods, adapters are exposed in the same way on any User Interface. BHoM functionality looks the same whether you use it from a programming script, a Grasshopper script, an Excel spreadsheet, or any other interface that can expose C#.

+


+

+

The approach to coding

+

The above code structure therefore enables flexibility, extensibility, transparency and readability.

+


+

A. Open, flexible data schemas

+

The base object class provides a CustomData Dictionary allowing dynamic assignment of any data type to any object. To the extent that a CustomObject is defined as an Empty Object.
+Default definitions for common objects can be curated and collectively agreed upon, however all are inherently flexible and extendible.

+


+

+

B. Ease of extensibility of functionality too

+

By structuring the code almost exclusively as extension methods in the Engine this enables new functionality to be added to existing objects without the requirement for derived types or indeed modification or recompilation of the base object. +This naturally opens the door wide to distributed development and customisation of new functionality on top of any existing base objects.

+


+

+

and finally, as highlighted, the above architecture and code design principles place mass participation and co-creation as central.

+


+

C. Transparency in code

+

The source code architecture, principles and terminologies are all open, exposed and reflected as a common language across the visual and text based environments as described.
+This is paramount for a seamless transition from a visual UI to code and vice versa with huge benefits to the developer in debugging and the designer in prototyping and well as a teaching aid to the lower level concepts behind the UI.

+



+

+

D. Human readable data

+

All objects natively serialisable based on JSON being compatible with MongoDB and standard data format for the web.

+


+

+

A shift from data encapsulation

+

Despite being one of the pillars of OOP, data encapsulation has been systematically eliminated in favor of a solution more transparent and more closely related to visual programming. This translates into a few interesting side-effects:

+

A. Node <--> Code correspondence

+

Since objects have no private members and functionality is represented as a collection of individual static methods, the conversion between code and visual programming nodes becomes a straight-forward exercise.

+



+

+

B. Shallow hierarchies

+

Most objects inherit directly from the BHoMObject class and polymorphism is expressed mainly through interfaces. This is made possible without duplication of code thanks to the lack of encapsulation and an engine designed around extension methods.

+



+

+

C. Orthogonal properties

+
+

With all object properties public, it is paramount for those to be independent from each other. This also means the objects are crafted with the minimal required information needed to construct them. All derived properties are exposed as methods in the engine.

+
+


+

+

Further Reading

+ +


+

+
+

+
+

1 By exception IImmutable objects are allowed where calculation of derived properties in the engine requires lazy computation.
+Section Profile is a good example

+

In addition some explicit casting and operator overrides etc. are also included in the BHoM definitions of some limited base objects.
+Node is a good example

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Basics/Coding fundamentals/The-BHoM-code-organisation/index.html b/Basics/Coding fundamentals/The-BHoM-code-organisation/index.html new file mode 100644 index 000000000..6f2ab3363 --- /dev/null +++ b/Basics/Coding fundamentals/The-BHoM-code-organisation/index.html @@ -0,0 +1,4411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + The BHoM code organisation - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

The BHoM code organisation

+

The programming code of BHoM is hosted under a GitHub organisation: https://github.com/BHoM.

+

The organisation hosts long list of things called Repositories. Most of them will have a name finishing with "_Toolkit". Foundational repositories are instead called BHoM, BHoM_Engine, BHoM_Adapter, BHoM_UI, among others. The Toolkits are the things that host the actual code, with specific terminologies (object Models, oMs), functionalities (Engines) and translators (Adapters).

+

img

+

Before we discuss in more details what is a repository and what it contains, let's take a step back and look at the different categories of code/functionality we can find inside them.

+

The 4 categories of code

+

If you ever have created your own tool or script, you must must have been exposed to the two dual aspects of computation: data and functionality. In excel, data would be the value of your cells and functionality would be the formulas or VB scripts. In Grasshopper and Dynamo, the functionality is made by the components, and the data is stored within parameters.

+

Data is generally representing specific concepts. For example, Grasshopper and Dynamo provide definitions for Points, Lines, etc., which are geometrical concepts. There are however a lot of objects that we manipulate regularly as engineers that are not defined out of the box in any of those programs. So our first category of code will focus on that: providing a list of properties that fully define each type of object we use. For example, we can all agree that a point would have three properties (X,Y, and Z) each representing to position of the point along one axis. This applies similarly to agree on the definitions of elements such as walls, spaces, speakers,...

+

Manipulators are the bespoke scripts, algorithms, equations, ... that we had to write ourselves to provide calculations not readily available. As engineers we have all had some of those custom made solutions lying around on our computer. Here we simply provide a central place to collect and store them in an organised way so we can all benefit from it.

+

The two categories above are called respectively oM (stands for object model) and Engine. They are all we need to extent our internal computational capability. That being said, we have no intention to reinvent the wheel by replacing external software like Revit, Robot, Tas, IES,... We are also keen to keep using the user interfaces that we already know like Excel, Grasshopper and Dynamo. We are therefore adding two more categories to our central code. Adapters to allow the exchange of data between our internal code and external softwares. UI plugins to typical programs like Grasshopper and Dynamo that expose all our code directly.

+

img

+

In summary, the 4 categories of code, you will find among those repositories are:

+
    +
  • +

    oM: Definitions of the data we manipulate (e.g. Beam, Wall, Speaker,…)

    +
  • +
  • +

    Engine: Our own custom tools, algorithms, data exploration & manipulation.

    +
  • +
  • +

    Adapters: Connections between the BHoM and engineering tools such as Revit, GSA, Tas, IES,... This is where BHoM objects are translated to and from the proprietary representation used in each of those tools.

    +
  • +
  • +

    UI: Expose the BHoM functionality through user interfaces such as Grasshopper, Dynamo and Excel.

    +
  • +
+

Dependency chain

+

image

+

The concept of a toolkit

+

The BHoM is designed to be extendable. We want anyone to be able to create a set of tools relevant to a specific task (e.g. linking to another external software, providing a set of discipline specific functionality, ...). This is where the repository come in. They are independent units of development with their own team of developers responsible for maintaining the code in the long run. We call them toolkits.

+

Internally, they will all follow the same conventions about the 4 categories of code defined above. To get slightly more into details regarding how that code is structure, let's talk for a second about how those different parts of the code are related to each other.

+
    +
  • +

    oM: You could see this as our base specialised vocabulary. It doesn't depend on anything else but everything else will rely on the definitions it contains.

    +
  • +
  • +

    Engine: Depends only on the oM. Since this is an internal engine, it doesn't have to be aware of any external software or UI.

    +
  • +
  • +

    Adapters: The adapter will depend on the oM for the objects definitions and on the engine for the conversion methods

    +
  • +
  • +

    UI: Depends on everything else since it will expose all the functionality above to the UI.

    +
  • +
+

Here's what it looks like in a diagram. To be concise, we will refer to this diagram as the diamond in the future.

+

img

+

Be aware that most of the toolkits will not implement all four categories. Let's look at a few user cases:

+
    +
  • +

    Adapter_Toolkit: E.g. Revit_Toolkit, TAS_Toolkit, GSA_Toolkit,… In there, you will very likely only implement the Adapter category (for the link with the external software) and the Engine category (for the conversion).

    +
  • +
  • +

    UI_Toolkit: E.g. Grasshopper_UI, Excel_UI,... In all likelihood, you will only have to worry about the UI category. You might create and Engine for calculations only relevant to that UI but, most of the time, you'll find it is not needed.

    +
  • +
  • +

    ProjectType_Toolkit: CableNetDesign_Toolkit, SportVenueEvent_Toolkit,… Focus on providing addition functionality specific to a project type. Provides addition object definitions in the oM and algorithms in the Engine. Nothing on the adapter or UI side is needed.

    +
  • +
+

You will find more details on the specific code structure and conventions to follow for each category in the Further reading section but this is probably enough detail for now.

+

Core repositories

+

So, what about the few repositories that don't end with _Toolkit then? Understandably, there is also a large collection of code that will be useful in multiple toolkits. All the code that fits that description will be stored in one of the Core repositories. You will find there is one repository for each category of code.

+

"But, but, why do you have an exploded diamond instead of a single repo for your core?? It would make things more consistent!" That is a valid point but the code in the Core is much larger than any toolkit. Repositories are used to distribute responsibilities between teams of people and to facilitate semi-isolated development. By splitting each category into its own repository, we enable focused sprints with a smaller risk of people stepping on each other's toes.

+

Note that, while toolkits will always depend on the core, the core should never depend on a toolkit. The toolkits are also fairly independent sets of code so there should be very few dependencies between them.

+

img

+

Further Reading

+

Now that you have a global view of the way the code and the repositories are organised, you might wonder how that translate into you actually writing code either on the core or on a toolkit. Here's where you can find more details on the way each category of code is structured and the conventions you need to follow:

+ + +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Basics/Installing-BHoM/index.html b/Basics/Installing-BHoM/index.html new file mode 100644 index 000000000..ba1b75928 --- /dev/null +++ b/Basics/Installing-BHoM/index.html @@ -0,0 +1,4381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Installing BHoM - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Installing BHoM

+

A BHoM installer is released quarterly and is subject to thorough testing.

+

Installation steps

+
    +
  1. Download the BHoM installer from bhom.xyz.
  2. +
  3. Make sure all instances of Rhino, Excel and Revit are closed before running the BHoM installer.
  4. +
  5. Run the BHoM installer. The installer does not require administrative privileges. If asked for permission, just click OK and proceed.
  6. +
  7. Check if BHoM was correctly installed:
  8. +
+
+

Check if BHoM is correctly installed

+
+
+
+

Open Grasshopper and verify that the BHoM tab is present.
+image

+

Click in any empty spot, then press CTRL+Shift+B. This should open up the BHoM menu.
+Try typing something there, like "Point". You should see a list of components.
+image

+
+
+

Open Excel and verify if the BHoM tab is present: +image

+

Click on any cell, then press CTRL+Shift+B. This should open up the BHoM menu.
+Try typing something there, like "Point". You should see a list of components.
+image

+
+
+
+
+

Installation troubleshooting / FAQ

+

Can't install – Error writing to file XXX: Verify you have access

+

If you get an error such as:

+

image

+

This can happen on Windows multi-user machines where a previous user had installed an old version of BHoM, and the current user that is trying to install it for himself does not have admin rights.

+

The solution is to delete the C:\ProgramData\BHoM folder.
+Unfortunately, if you don't have admin rights, the only way is to ask your Administrator to delete it.

+

Once the folder has been deleted, any user (also without admin rights) will be able to install BHoM correctly.

+

Developers and contributors 🤖

+

Developers, general contributors, as well as those who need a special version of a toolkit, may need to compile the source code themselves.
+Please read Getting started for developers for more info.

+
+

Alpha installer

+

We have a CI/CD pipeline that produces Daily alpha versions of the installer.

+

At the moment, we don't currently publish Alpha installers due to some techincal issues.
+Please get in touch with us, for example opening a GitHub discussion, if you'd like us to proritize them being published again.
+You can also ask a member of the team to share an alpha installer, if required for some particular development exercise.

+

The Alpha installer includes the most up-to-date changes present on each repository develop branch (or, in absence of that, the main branch). +Testing before merging to develop (or main) is always conducted, so a good level of stability can always be expected, although integration tests are limited on this stage. +Certain features may be subject to modifications or corrections until they become permanet features after the beta release.

+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Basics/The-BHoM-Toolkit/index.html b/Basics/The-BHoM-Toolkit/index.html new file mode 100644 index 000000000..bb533d327 --- /dev/null +++ b/Basics/The-BHoM-Toolkit/index.html @@ -0,0 +1,4410 @@ + + + + + + + + + + + + + + + + + + + + + + + + + The BHoM Toolkit - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

The BHoM Toolkit

+

A Toolkit is a set of tools (definitions, functionality, and connectivity) used for a specific purpose.

+

For example, you will find a Robot_Toolkit to do structural analysis with Autodesk Robot; similarly, you can find a Revit_Toolkit, a LifeCycleAssessment_Toolkit, and many many others.

+

Structure of a Toolkit

+

In short, a Toolkit can contain one or more of the following projects:

+
    +
  • A BHoM_oM project, that contains the definitions specific to your Toolkit (the types, or the schema, needed for your purposes).
  • +
  • A BHoM_Engine project, that contains the functionality specific to your Toolkit.
  • +
  • A BHoM_Adapter project, that contains the connectivity required to interface with an external software.
  • +
+

If you are an User, head to one of the sections linked above to learn more (oM, Engine, Adapter).

+

If you are interested in programming, creating your own new Toolkit, or contributing to the code of an existing one, keep reading.

+

Implementing a new Toolkit

+

In order to implement a new Toolkit, we prepared a Toolkit Template that prepares a Visual Studio solution with all the scaffolding done for you: create an new Toolkit using the BHoM Toolkit Template.

+

Create a new software Toolkit using the BHoM Toolkit Template

+

Create a new Toolkit repository

+

Use the template repository to create a new repository. See the readme there.

+

Implement the oM

+

The oM should contain property-only classes that make the schema for your Toolkit. All functionality should be placed in the Engine. +Functionality that is specific to a class should be defined in the Engine as an extension method.

+

See The Object Model and The Engine for more information.

+

Implement the Engine

+

The Engine should contain the functions applicable to the objects you've defined in the oM.

+

See The Engine for more information.

+

Implement the Adapter

+

See the dedicated page to Implementing an Adapter.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Basics/Using-the-BHoM/index.html b/Basics/Using-the-BHoM/index.html new file mode 100644 index 000000000..38e6356a9 --- /dev/null +++ b/Basics/Using-the-BHoM/index.html @@ -0,0 +1,4456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Using the BHoM - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Using the BHoM

+ +

Overview

+

For this introduction, we will be using Grasshopper as a model but be aware that the same general principles will apply to other UIs (Dynamo, Excel, ...) too.

+

The UI layer has been designed so that it will automatically pick everything implemented in the BHoM, the Engines and the Adapters without the need to change anything on the code of the UI. This means that, instead of having one component for every single piece of functionality, it will group them under common components. This way, the number of component there doesn't have to change when more functionality is added to the rest of the code. Here's what it looks like in Grasshopper:

+

img

+

In a few words, the oM section is for creating object, the Engine section is for manipulating objects, using them to derive information, or running some form of calculation, and the Adapter section is for exchanging data with external softwares.

+

Key Concept

+

In order to explain how most of those components work, let's start with the Create BHoM Object that can be found in the oM section:

+

img

+

As you can see, you first drop a dummy component on the canvas that has no input nor output. You then select in its menu what you want it to be to turn it into its final form.

+

The principle is exactly the same for the Compute, Convert, Modify, and Query components in the Engine section as well as for the Create Adapter and Create Query components in the Adapter section. Here's the example for the Create and Create Adapter components:

+

img

+

img

+

Search Menus

+

Notice that there are a couple more ways to create the final component you need:

+
    +
  • Use the search menu from the component:
  • +
+

img

+
    +
  • Use the Ctrl+Shift+B shortcut and then do a search from that menu
  • +
+

img

+

If you want to search for something that include a series of words, just separate them by a space like done above.

+

Create Custom Objects

+

We have seen how to Create BHoM objects using the Create BHoMObject component. There will be situations where you need a type of object that is not part of the BHoM (yet?). For this, we have the CustomObject component:

+

img

+

This component allows you to define your own objects with a custom set of properties. You will notice that the Component start with two inputs: Name and Tags. This is because a CustomObject is also a BHoMObject and every BHoMObject has a property for Name and a property for Tags. IF you don't want to use those two, just remove them.

+

Usually, that component is automatically figuring out the type of each property based on the data plugged in its inputs. There might be times when it got it wrong. For that reason, you can always specify manually the type of each input from its context menu. This is especially useful when the input is a list. In that case, just tick the box for List to tell Grasshopper you want that list as a single input instead of one value in the list per object.

+

img

+

Alternative Ways to Create Known Types of Objects

+

The CreateObject component provides a series of recommended ways to create known objects. Those correspond to the methods defined in the Create section of the BHoM_Engine. There might be rare cases where you cannot find a constructor that suits your needs. In that situation, you can use the CreateCustom component to define your own way to build a known object, just select the type of object you want to create from the contextual menu and select your own inputs. Inputs that are not properties of the object will be stored in CustomData.

+

+

Other Types of Objects

+

Sometimes, you will find a component requiring an input that is not a BHoM object and not something you can create in Grasshopper either. The Enum, Type and Dictionary components are exactly there to cover those situations. One case you will probably encounter soon is when using the FilterQuery component from the Adapter section.

+

img

+

The Dictionary and Enum work from the same principle: you select their final form from their menu.

+

The Enum component has a slightly different form though:

+

img

+

The Data component shown above is allowing you to select data from one of our static databases. Its output type will therefore vary based on the data you select. Those will generally be a BHoM object though.

+

Engine components

+

The 4 components on the left correspond to the 4 categories of methods you can find in the Engine: Compute, Convert, Modify, and Query (Create being in the oM section).

+

The 3 components in the middle are for extracting or updating properties of BHoM objects. The one you will probably use most of the time is the Explode component:

+

img

+

The last two components are for converting any BHoM object to and from JSON. This stand for JavaScript Object Notation. This is the langage we use when we represent BHoM objects as string. Unless you see straight away how those components can be of used to you, you can safely ignore them.

+

Adapter components

+

We have already mentioned the Create Adapter and the Create Query components from the right part of the Adapter section. The 6 components on the left part correspond to the 6 operations provided by the interface of every BHoM adapter: Push, Pull, UpdateProperty, Delete, Excecute and Move. Most likely than not, you will generally use the Push and Pull components so we'll show how those two work here.

+

img

+

Here we have the Socket adapter to send data across and get it back. Obviously, you would use sockets to send data between two different UIs or computers instead of just within Grasshopper but this example is just to show how the Push and Pull components are working.

+

Things to Remember

+

While we have shown quite a few things here, the main thing to remember is that most of the components in our UIs require you to select something from their menu before they switch to their final state. You can do that by either navigation the menu tree or using the search box. Those menu trees are organised exactly the same way has the code you will find on GitHub. You can also use Ctrl+Shift+B to create the final component directly.

+

On top of that concept, remember the CustomObject and Explode components. They are a very convenient way to pack and unpack groups of data.

+

From there, the best way to learn how to use those tools is to play with them in your UI of choice or to browse the documentation provided on the Wiki of each repository.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Basics/index.html b/Basics/index.html new file mode 100644 index 000000000..0d36c038f --- /dev/null +++ b/Basics/index.html @@ -0,0 +1,4353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + What is the BHoM for? - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

What is the BHoM for?

+

Linking Meanings

+

The variety of AEC (Architecture, Engineering and Construction) commercial software does not always provide solutions to all needs, especially when it comes to collaborating with many people. We frequently encounter particular problems that require some special functionality not offered by any specific software, and we feel the need to implement it ourselves via, for example, custom scripts, or spreadsheets, or macros.

+

BHoM proposes a central Object Model, which is a schema (in other words, a dictionary of terms) on which functionality can be built. By agreeing on common terminology, the output of a script created by one person can easily be used as the input for another script.

+

So, at its core, the BHoM contains a collection of object definitions that we all agree on. The definitions are created by the same domain experts that use them everyday (e.g. Structural Engineers, Electrical Engineers, Architects...), and they are categorised per discipline (e.g. Structure, Architecture, ...). Each definition is simply a list of properties that an object should have (e.g. wall, beam, speaker, panel,...). We call this collection of definitions the object Models (oMs).

+

By extension, the BHoM (Building and Habitats object Model) is the collection of these object Models and the functionality built on top of them.

+

Linking Software

+

Across the AEC industry, regardless of our discipline, we have to work with multiple softwares during the course of any given project. Since there is rarely a simple solution to transfer the data from one software to another, we usually end up either doing it manually each time or writing some bespoke script to automate the transfer. Things get even more complex when we work across disciplines and with other collaborators. When the number of software to deal with becomes more than just a few, this one-to-one translation solution quickly becomes intractable.

+

This is where the BHoM comes in. Thanks to the central common language, it is possible to interoperate between many different programs. Instead of creating translators between every possible pair of applications, you just have to write one single translator between BHoM and a target software, to then connect to all the others.

+

We call those translators Adapters.

+

+

Linking functionality

+

Most often, AEC industry experts create ad-hoc functions and tools when the need arises. Common examples are large, complex spreadsheets, VBA macros for Excel, but also small and large User Interface programs written in C#, Python or other tools.

+

In such a large sector, most problems you encounter are likely to have been solved before by someone in your organisation, or outside. What frequently happens is that the wheel gets reinvented. Additionally, when a script is created, it often exists in isolation, and is used only by a small group of people.

+

Let's take the example of a user wanting to create some functionality in an Excel macro. This has several issues:

+
    +
  • +

    Maintanability issues. Frequently, only the original creator of a Macro knows how to develop or maintain it. If they are not around and a problem arises, the Macro is often just thrown away.

    +
  • +
  • +

    Shareability issues. For example, if the creator of a Macro that performs some function in a spreadsheet wants to share this functionality with one of their clients, they end up sharing the spreadsheet with the Macro in it, effectively sharing the source code (the fruit of their hard work), which could end up being copied.

    +
  • +
  • +

    Scalability issues. Macros are hard to scale. If you need to add more features or more complex functionality in a Macro, the code soon becomes unmanageable.

    +
  • +
  • +

    Collaboration issues. Collaborating on the code written in a Macro is a mess. Only one person can work on it at the same time. In order to merge the work of different people into a same Macro spreadsheet, either a library is created, or copy-paste is required.

    +
  • +
+

BHoM proposes to create a common library of functionality in Engines, which are simply tidy collections of functions. Like Adapters, Engines can be stored in Toolkits, which are simply GitHub repositories. GitHub repositories make it easy to share and collaborate on code (or not share, if privacy is chosen). BHoM makes it very easy to expose any functionality written in an Engine to Excel, Grasshopper, or other interfaces.

+

Thanks to the BHoM being exposed in various UIs such as Grasshopper and Excel, you don't even need to know how to write code to use the functionality created by other people.

+

+

Linking people

+

By sharing terminology, functionality, and connectivity to software, BHoM allows us to shift the attention from "connecting data" to "connecting people together"!

+

BHoM also embraces Open Source as a practice: there is infinite value in opening up development efforts to the larger AEC community. Sharing effort pays big time!

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Creating-a-New-Repo/index.html b/Contributing/Creating-a-New-Repo/index.html new file mode 100644 index 000000000..6d631d1d8 --- /dev/null +++ b/Contributing/Creating-a-New-Repo/index.html @@ -0,0 +1,4318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Creating a new repository - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Creating a new repository

+

All of the repositories within the BHoM organisation contain only curated and strongly controlled code. +The aim is to provide a coherent set of tools that are all compatible with each other. As well as clear and robust quality control, review and testing procedures - enabling release across a wider part of the practice.

+

For more details on the organisation of the BHoM repos See Overview of the Structure of the BHoM and the other links from within What is the BHoM?

+

Setting up a new Repo

+

Following the clarification of the best location for different code above - instructions on how to create a new BHoM Repo with the correct settings:

+
    +
  1. +

    Name the Repo SoftwareNameOrFocus _Toolkit. It will always end in Toolkit see explaination here

    +
  2. +
  3. +

    Make sure the Public option is selected.

    +
  4. +
  5. +

    Under Settings -> Options. Ensure only Rebase merging is enabled

    +
  6. +
+

image

+
    +
  1. +

    Add a Team under Collaborators and teams

    +
  2. +
  3. +

    Under Branches. Set the main Branch as protected with the following settings (click edit on the right-hand side of the listed main branch)

    +
  4. +
+

image

+
    +
  1. If you don't have a team for that repo yet, you can create it here. Make sure the team has the same name as the repo and that you have added the repo into its list of repositories with the the "Write" access level. Now you should be able to link it in the branch setup page above.
  2. +
+

Initial Content

+

TODO: provide details about: +- Readme file +- License file +- gitignore file +- VS template

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Editing-the-documentation/index.html b/Contributing/Editing-the-documentation/index.html new file mode 100644 index 000000000..bbbe33a16 --- /dev/null +++ b/Contributing/Editing-the-documentation/index.html @@ -0,0 +1,4643 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Editing the documentation - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Editing the documentation

+

This documentation is simply a set of Markdown documents, stored and organised in the Documentation repository. +The markdown documents are then automatically mapped to a web page every time a Push to the main branch of the Documentation repository is done. See below for technical details on how this is achieved.

+

Depending on your account permissions, you should be able to commit directly to main, or a Pull Request will be required to perform the changes.

+

Minor modifications

+

For small modifications, you can click the pencil ✏️ icon on the top-right of the page. This will bring you to the Github Markdown editor.

+

Avoid this for non-minor changes. Limit this approach to e.g. correcting typos, rephrasing sentences for clarity, adding short sentences, fixing URLs.

+

General modifications

+

In order to edit the documentation, you need to:

+
    +
  1. Clone the documentation repository on your machine
  2. +
  3. Navigate to the docs folder, and edit a markdown file or add new markdown files.
  4. +
+

The enhanced markdown

+

The documentation markdown can incorporate non-markdown content. You can embed:

+
    +
  • +

    HTML blocks with embedded functionality, e.g. (click on details to see!): +
    +

    + + + +
    +

    +
  • +
  • +

    Latex/Mathjax, e.g. \(f(x) = x^2\) (enclose the formula between single $ for inline and double $$ for block).

    +
  • +
+
+

Tip

+

Many more features are available to spice up the look of the documentation and help you convey information. +See the available customisations of Material for Mkdocs: setup and Markdocs extra elements.

+
+

Using a text editor to edit the documentation

+

We recommend using either Visual Studio Code or Markdown Monster to edit the documentation.

+

With Visual Studio Code you can preview the markdown while editing.

+

Adding pages

+

If you want to add a page, just add an new markdown document.

+

The first H1 header (#) of the page will be taken as the title of the corresponding webpage.

+

Each header will be reflected in the navigation menu on the right hand side of the page.

+

Linking to other documentation pages

+ +

Links to other documentation pages should be relative URLs (starting with a /) where the first slash must be followed with the documentation folder. Some examples:

+
    +
  • To link to the Introduction to BHoM_Adapter page, you can provide this link: /documentation/BHoM_Adapter/Introduction-to-the-BHoM_Adapter.
  • +
  • To link to the IsValidDataset page, you can provide this link: /documentation/DevOps/Code%20Compliance%20and%20CI/Compliance%20Checks/IsValidDataset.
  • +
+
+

Note

+

This way of providing URL to pages is required because MkDocs reflects the markdown files starting from the root documentation.

+
+
+

Tip

+

If using Visual Studio Code, enable Error Reporting on Markdown files and Link Validation in settings. VS Code will then check the validity of local links and cross referencing across your Markdown files, flagging warnings where links are invalid. VS Code Link Validation

+
+ +

If you are editing a specific nested page you can also use URLs relative to the current page. Some examples: +- To link to the Getting started for developers page, relative to this current page (Editing-the-documentation), you can provide: ../Getting-started-for-developers.

+

Folders

+

Folders behave as groups for sub-pages and are reflected into the left menu of the website.

+

A folder may contain one markdown file called index.md; if it exists, that file is taken to be the first page of the folder when viewed from the website.

+

For information on how to sort the pages, see below.

+

Previewing the website before pushing to the repository

+

You may want to preview how your markdown documents will appear in the automatically-generated website. In order to do this, you can use mkdocs from command line.

+

You need to:

+
    +
  1. Have Python and PIP installed on your machine. Install mkdocs by running +
    pip install mkdocs
    +
  2. +
  3. You will also need to ensure you have the MkDocs extensions and plugins that we are utilising installed. These can also be installed using pip by running the following: +
    pip install mkdocs-material 
    +pip install mkdocs-awesome-pages-plugin
    +pip install mkdocs-git-revision-date-localized-plugin
    +
  4. +
  5. Navigate to your locally cloned documentation repository folder.
  6. +
  7. In that location, invoke from command line:
    +python -m mkdocs serve
  8. +
+

Mkdocs should spin up a local server and you should be able to connect to http://localhost:8000/ in your browser to display the documentation website. Any change to your local file will be hot-reloaded into the webpage.

+

Website configurations

+

As mentioned, the Markdown documents are transformed into a proper web page thanks to mkdocs every time a Push to the main branch of the documentation repository is done.

+

The web page can be configured by configuring mkdocs and related dependencies.

+

Dependencies

+

On top of mkdocs, we also use:

+ +

Customising the ordering of the pages in the menu

+

You just need to add a .pages text file in the specific folder where you want to sort out your pages. For an example, see the .pages file in the docs folder.

+

You can edit the .page file according to mkdocs awesome pages plugin.

+

Customising the appearance of the documentation

+

See the available customisations of Material for Mkdocs: setup and extra elements.

+

Github actions configuration

+

Every time a push to this repository is done, a GitHub action kicks in and calls:

+
    +
  • mkdocs functionality to transform markdown to HTML
  • +
  • mkdocs-material functionality, which expands the markdown translation of mkdocs with more features
  • +
  • mkdocs awesome pages plugin functionality, which gives extra configs on top of mkdocs.
  • +
+

The actions are configured as described in https://squidfunk.github.io/mkdocs-material/publishing-your-site/.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Icons/index.html b/Contributing/Icons/index.html new file mode 100644 index 000000000..026b3dafe --- /dev/null +++ b/Contributing/Icons/index.html @@ -0,0 +1,4332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Icons - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Icons

+ +

General Guidelines

+

As a general rule, the icons are stored in the BHoM_UI repository. All other UIs will then use those directly so there is no need for each UI to create its own icons.

+

Specific Toolkit Icons

+

TODO

+

Software Specific Guidelines

+
    +
  • Grasshopper icons are 24x24px png files and follow David Ruttens Guidelines. +A recommended workflow is to export a 24x24px png from Inkscape with a 20x20px vector inside it and then use Gimp to add a dropshadow.
  • +
+

Useful Software

+
    +
  • +

    Gimp, a free image editor.

    +
  • +
  • +

    Inkscape, a free vector graphics editor.

    +
  • +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Open-Sourcing-Procedure/index.html b/Contributing/Open-Sourcing-Procedure/index.html new file mode 100644 index 000000000..ad1f33906 --- /dev/null +++ b/Contributing/Open-Sourcing-Procedure/index.html @@ -0,0 +1,4303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Open Sourcing Procedure - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Open Sourcing Procedure

+ +

Preparation of the code and repository

+

Tasks to be completed before migration to public organisation.
+To be carried out/reviewed by the Repository Owner:

+ +
    +
  • Ensure repository has valid licence file.
    + BHoM defaults to LGPL v3 https://github.com/BHoM/BHoM/blob/master/LICENSE.
    + Repositories can naturally be licenced differently, but by exception only and through coordination with BHoM Organisation Administrators. In addition this will also require modifications to the repo's copyright headers in every code file.
  • +
  • All code files must have a valid BHoM copyright header compatible with licencing (see point above) + See here for the default https://github.com/BHoM/BHoM/blob/master/COPYRIGHT_HEADER.txt
  • +
  • Assembly information and the included copyright must also conform to BHoM standards.
    + Such as [assembly: AssemblyCopyright("Copyright © https://github.com/BHoM")]
  • +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Pull-Requests/index.html b/Contributing/Pull-Requests/index.html new file mode 100644 index 000000000..0fc0303e6 --- /dev/null +++ b/Contributing/Pull-Requests/index.html @@ -0,0 +1,4365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Pull Requests - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Pull Requests

+ +

Introduction

+

Pull Requests are the primary mechanism of resolving issues and deploying new code to users. They provide us an opportunity to review and reflect on the proposed changes and ensure they meet the criteria of the issue and the broader agenda without introducing any major concerns with bugs or broken functionality.

+

Pull Requests should be seen as a collaborative process during the review stage. Raising a pull request is not a guarantee that proposed changes will be deployed to the main branch, but changes can only be deployed via a pull request mechanism.

+

Raising a Pull Request

+

A pull request can be raised at any stage of the development cycle, either as draft, WIP (Work In Progress) or as ready for review depending on the state of the proposed changes. A pull request can be reviewed at any time by anybody, but it is good practice to request a review from key individuals working in that area (for example, a DevOps reviewer when making changes to the core, or a geometry reviewer if making changes to the Geometry oM/Engine).

+

A raised pull request should have the following features within it - these are provided as headings in the pull request template to complete when raising the pull request:

+
    +
  • Clear identification of any dependant pull requests that this work is relying on to operate - for example, if your work in a toolkit depends on a change in BHoM_Engine, then in the toolkit pull request there should be a clear link to the BHoM_Engine at the top of your toolkit pull request
  • +
  • Linked issues that are being resolved by this pull request - where there are multiple pull requests in a series, at least one of them needs to be referencing an issue that clearly outlines what needs to change in the code. The pull request should aim to solve just the issue outlined. One pull request can resolve several issues at once if needed
  • +
  • Test scripts - either reference to data-driven unit-tests (which BHoMBot will then run) or links to test scripts built in a BHoM suitable UI, or reference to the issue which may contain test scripts when the issue was raised.
  • +
  • Change log - see our change log guidance
  • +
  • Additional comments - anything extra you feel will help the reviewers in reviewing your pull request
  • +
+

Categorising a Pull Request

+

All pull requests should set a label defining the type of pull request, this is to categorise pull requests when producing the change log.

+

Pull Request activity

+

A pull request is a mark of coding resource that was used to try to solve a given issue (or issues). As such, it should be viewed that a pull request is aiming to deploy that code to the main branch (pending review) unless it is a speculative piece of work looking at possible options for a given idea. However, due to the pace of change within the BHoM ecosystem, a pull request can be difficult to deploy if it is left for too long. By rule of thumb, a pull request should aim to be deployed within one sprint of time (raised, reviewed, amended per review, and deployed), to avoid hanging work that isn't deployable.

+

To keep our review requirements focused on the latest workload, each milestone will have a Pull Request closure day if deemed necessary by DevOps.

+

Stale Pull requests

+

Pull requests which have not had any activity in 3 months are deemed to have gone stale.

+

Pull requests which have not had any activity in 6 months will be closed to avoid drawing review resource if no activity has happened. Activity can be defined as committing code to the pull request, commenting on the pull request (even if just with an update stating the work is still desired but there's a lack of resource to close it out currently) or any activity which shows up on the pull request within the time period examined.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Resolving-an-Issue/index.html b/Contributing/Resolving-an-Issue/index.html new file mode 100644 index 000000000..21a53dca0 --- /dev/null +++ b/Contributing/Resolving-an-Issue/index.html @@ -0,0 +1,4439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Resolving an Issue - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Resolving an Issue

+ +

Introduction

+

Open issues are reviewed weekly and the most critical ones are assigned to +specific people as part of their weekly tasks. That task of resolving an issue +is called a sprint. If you need more information on how those issues are being +created, check this +page.

+

A person in charge of that issue will then create a new branch, write the code +necessary to solve that issue (with potentially multiple commits on that +branch) and then submit a pull request to merge that branch back to the +main development branch. This pull request will be reviewed by other developers +and the code on that branch will potentially be edited to match everyone's +satisfaction. The pull request will then be approved and the branch will be +merged with the main one. For more detailed explanations on the process, check +this short guide

+

If you haven't already make sure you read Using the SCRUM +Board - it's easy!

+

Overview of important steps to successful coding in the BHoM

+

A. Preparatory work

+

Preparatory work is mandatory. Before doing anything review the activity in relevant repos and speak to team + working in similar areas of the code. You can not start working on any part + of the code before you have checked that there are no Pull Requests open for the Project or for the + entire repo you want to modify. See naming + convention

+

If the above steps are not fulfilled, coordinate with the person + working on that branch. Either work on the same branch if possible, + expanding the pull request to cover more issues (make sure you link all + issues in the conversation of the pull request), or work + locally + on your machine until the other branch is merged. + 1. If you choose to work on the same pull request, make sure any + conversation being done is done publicly on Github to ease to process of + the reviewers. + 2. If Urgent and you cannot coordinate + work locally, but do not branch yet

+

B. Solving the issue

+
    +
  1. +

    Select an Issue or raise one.

    +
  2. +
  3. +

    Create a Branch for the specific Issue - using the correct naming + convention + and considering to branch or not to + branch?

    +
  4. +
  5. +

    As soon as you pushed your first commit, open a Draft Pull Request, and add the card to the Project SCRUM Board. This action will communicate to others that the repo is now locked and avoids conflicts.

    +
  6. +
  7. +

    Push each individual Commit - keeping commits as specific and frequent + as possible. Always review what files you are committing. And make sure your + sprint is not drifting from the original issue.

    +
  8. +
  9. +

    When your code is ready to be reviewed, change the stage of the pull request by marking the pull request as ready for review. Also remember to:

    +
  10. +
  11. use the fixes + keyword + to reference your issue
  12. +
  13. assign your reviewers,
  14. +
  15. include links to any test files that have been used to assist with + swift review process,
  16. +
  17. +

    it is also useful to add any comments and context that can be helpful in + the review process

    +
  18. +
  19. +

    Work with your reviewer to close out

    +
  20. +
  21. +

    On successful Merge and Rebase high five the person next to you! :tada:

    +
  22. +
+

Branch naming conventions

+

See the DevOps branching strategy.

+

Breaking changes

+

See our versioning strategy for more information on avoiding breaking changes.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Submitting-an-Issue/index.html b/Contributing/Submitting-an-Issue/index.html new file mode 100644 index 000000000..2a83c4b7d --- /dev/null +++ b/Contributing/Submitting-an-Issue/index.html @@ -0,0 +1,4373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Submitting an Issue - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Submitting an Issue

+ +

Introduction

+

Issues are used to keep track of all the requests for bug fixing, new features,... They can be created inside each repository and optionally assigned to a specific person.

+

A good short guide on issues is available here

+

Create a New Issue

+
    +
  • +

    On github, go the the repository that needs modifications and select the Issues tab.

    +
  • +
  • +

    Click on the green New Issue button on the top right corner.

    +
  • +
+

img

+
    +
  • +

    Fill in the title. The name should be Description or ProjectName - Description depending on whether the issue needs changes in the entire repo or in a specific Visual studio project. If you don't know which one it is, just use the repository name.

    +
  • +
  • +

    and fill in the description. This is using markdown so you can format your message like you would a wiki page. You can also attached files simply by dropping them in the message area.

    +
  • +
  • +

    Please be specific as you can with both the title description and the body text to give others as much information and context around your proposed issue.

    +
  • +
  • +

    If you are not already a BHoM Collaborator or part of the Organisation, then you are good to go. Press Submit New Issue. A collaborator already with write access will pick up the issue and Label/Assign.

    +
  • +
+

Collaborator Issue flagging and assignment

+
    +
  • +

    As a collaborator or maintainer with write access - it is important to assign labels, as well as assignees if at all possible, for issues as you create them - as well as new issues created by others outside of the organisation to assist with triaging

    +
  • +
  • +

    If you already know who is going to handle that issue, you can assign it to that person by using Assignees on the right side of the screen. Otherwise, just leave it blank.

    +
  • +
  • +

    Make sure you select a Label to specify the type of issue you have (more about this on the next section).

    +
  • +
  • +

    If you request is linked to a very specific deadline, you can also pick a Milestone from the list.

    +
  • +
+

img

+

Choose a Label

+

The two main categories of labels are feature and bug. Features are for requesting functionality that doesn't exist yet. If there is similar functionality already but not matching 100% what you need (e.g. missing inputs or outputs you would need), this is also a feature request. Bug is for when that functionality exists but provides an incorrect result or crashes.

+

For both of those categories, we have 3 levels of importance:

+
    +
  • Critical: It is simply impossible to continue without either that feature or fixing that bug. No workaround exists using alternative methods or a quick self-made script.
  • +
  • Regular: This is slowing down progress. There is a workaround but it is not exactly ideal.
  • +
  • Minor: You noticed a missing feature or a bug but it doesn't stop/slow you in your current work.
  • +
+

img

+

Outside of those two main groups, 4 more labels are provided:

+
    +
  • Compliance: This is for people working directly within the code. You found some code that doesn't follow th rules we have in place for how the code should be structured and would like this to be fixed.
  • +
  • Question: When you don't have anything specific you need to be changed but would like some clarification on a specific point or would like to start a debate.
  • +
  • Test_script: You have created some new functionality and would like it to have its own set of automatic testing scripts to make sure it is regularly checked. Notice that you have to raise the issue where the test scripts will be written, NOT where the code to be tested is.
  • +
  • Documentation: You find the documentation about a specific part of the code lacking. As for the test_script label, you need to raise the issue in the repository where the documentation is going to be created.
  • +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Using-Git/index.html b/Contributing/Using-Git/index.html new file mode 100644 index 000000000..03e825943 --- /dev/null +++ b/Contributing/Using-Git/index.html @@ -0,0 +1,4318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + How to use Git - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

How to use Git

+

Introduction

+

This documentation will be focused on the use of Git Bash.

+

The first step is to create a space on your computer where you want all your local files to be stored. Now you want to create different repositories (repos) in this folder. Do this by opening up git bash and using git clone (web address). A good list of repos for getting started can be found here.

+

Pushing code changes to GitHub

+

Before getting started it is recommended to read through this first.

+

Start off by creating a new branch with an appropriate name.

+

You create a new branch with git checkout -b (name of the branch). Make sure that you are on develop when creating a new branch to prevent branches created from other branches. +It is now time for you to do the changes you wish to do. When you are satisfied with everything it is time make a commit. You should always rebuild the code to make sure that it compiles, and if needed test out the code before pushing it to GitHub.

+

Start by running git status which will show you, in red, all of the files that has been changed. If everything looks alright use git add . which adds all of the files to the commit. If you wish to only add selected files you can use git add (name of the file) for the files you wish to include.

+

Once the files are added it can be a good idea to double-check using git status again, and the included files should now be showed in green instead of red. +Then it is time to use commit these changes with git commit -m ("message"). Keep in mind that this message will be shown on GitHub along the commit so a somewhat brief explanation of what is included in the commit can be a good idea.

+

Finally it is time to actually push the commit to GitHub with git push origin (branch name). It is now possible to create a pull request on GitHub. +If you were to need to make any more changes before the PR is merged just make sure you are on your branch for that feature, (no need to create a new branch) and do the necessary changes and then start the push process again, starting with git status and git add ..

+

Avoiding conflicts

+

In order to avoid conflicts when creating pull requests make sure that the repository you are working on is up to date. +Make sure you are on the develop branch by using git checkout develop +Start off by using git fetch origin which gets updates from other repositories and then git pull origin (branch name) to update your code from others.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/Using-the-SCRUM-Board/index.html b/Contributing/Using-the-SCRUM-Board/index.html new file mode 100644 index 000000000..e624cb5b0 --- /dev/null +++ b/Contributing/Using-the-SCRUM-Board/index.html @@ -0,0 +1,4420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Using the SCRUM Board - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Using the SCRUM Board

+ +

Introduction

+

To keep things organised and avoid stepping on each other's toes, we are relying on the GitHub Project SCRUM Board. The Project SCRUM Board is the way we communicate, the tool we use to have a bigger picture of what is happening, and the way you will keep records of your work into the BHoM. +Since the the Project SCRUM Board is fully automatised, it is read-only and represents a view on what is happening across all the BHoM repositories.

+

img.

+

Each card that you see there corresponds to an issue raised in one of the repos. From the moment it is created to the time when that issue has been completely resolved, the corresponding cards, i.e. the issue card and the associated pull-request card, will go through the different columns of this board.

+

Creating a Card

+

The best way to create a card is to create an issue in the corresponding repository and add it to "SCRUM Development Board Planning" project. The card will automatically appear in the most appropriate column.

+

Although this is not recommended, if you want to create the card from the project board itself, see the GitHub's help page Adding issues and pull requests to a project board. Be mindful that when you convert the card to issue, it should follow the guidelines described in Submitting an Issue

+

SCRUM Board Columns

+

Priority this Sprint

+

This column contains only issue cards. Once an issue has been assigned to a person as part of his/her tasks for the week, the card can be added to the "SCRUM Development Board Planning" project. This action will place the card - an issue card - into the "Priority this Sprint" column automatically. If the card/issue was not assigned to anyone at that time, it will then be assigned to that person. You can see who has been assigned the issue by looking at the avatar at the bottom right of the card.

+

In Progress

+

This column contains only pull request cards. A card is in this column when a person starts working on the corresponding issue. New pull requests that are added to the "SCRUM Development Board Planning" project will automatically appear here. Normally, only one card per person should be in that column at a time.

+ + +

Cards in that column are also locking the repository or the project it targets. This means that nobody is allowed to start editing code in that repository while a card is in the In Progress or Review in Progress column. This also means that you can only add a card in that column if there is not already a card locking the same repository. Coordinate with the card's owner if this is the case.

+

Review in Progress

+

This column contains only pull request cards. Once the pull request has been reviewed, and a reviewer requested a change, the automation will move the card from the In Progress column into this one.

+

Reviewer approved

+

This column contains only pull request cards. Once the changes in the pull request have been accepted by the minimum number of reviewers required, it will be moved into this column. When a pull request is in this column, it is ready to be merged, unless a label do-not-merge is on it.

+

Completed

+

Once the pull request has been merged into the master branch and the issue closed, the card is moved the the Completed column where it will be discussed in the next planning call. Notice that, once an issue is closed, the logo at the top left of the card has turn red. The Completed column is the only one that should have cards in that state.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Contributing/posting-test-files/index.html b/Contributing/posting-test-files/index.html new file mode 100644 index 000000000..3910b04d9 --- /dev/null +++ b/Contributing/posting-test-files/index.html @@ -0,0 +1,4296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Posting test files - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Posting test files

+

Posting test files in GitHub Issues and Pull Requests (PR) is important, because they allow to reproduce the problem you may have or the feature being implemented.

+

Please remember that there are also other means of testing the code (e.g. code Unit tests; automated Data-driven equality unit tests), so some PRs may not expose any test file. However, if you only know how to reproduce your issue or wanted feature via a script (e.g. a Grasshopper script), or if you think that the PR/issue would benefit from it, then you should post it in the body of your issue (or PR, where applicable).

+

How to post a test file

+
    +
  • Create your test script (e.g. in Grasshopper).
  • +
  • Save your test script in a file.
  • +
  • Zip your test script file.
  • +
  • Drag and drop the test script file in the body of the GitHub issue or PR.
  • +
+

Large test files

+

The zipped test file must be less than 50 MB (GitHub size limit), and in general should be less than 10 MB. +If your test file is larger than that, it means that you've embedded (internalised) too much testing data. You should simplify the test script to use the minimal amount of data necessary to reproduce the problem. +If your test script purposedly targets a large model, then the script should only hold a reference to such model (e.g. a link to it) and the model should be uploaded via another file hosting service.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/BHoM releases/index.html b/DevOps/BHoM releases/index.html new file mode 100644 index 000000000..dbbf04840 --- /dev/null +++ b/DevOps/BHoM releases/index.html @@ -0,0 +1,4461 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM releases - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM releases

+

The BHoM is released as a complete package, with the individual BHoM libraries and its toolkits all versioned together. This is to ensure ease of tracking compatibility across the number of dependant repositories.

+

BHoM versions are therefore named using the following convention: major.minor.α/β.increment

+

Major version

+

A major version denotes some fundamental change in the BHoM framework. Targeted approximately yearly.

+

Minor version

+

Minor versions denote the more frequently planned development cycles and the release of new features/issues, as per individual development road maps and SCRUM planning. Targeted every couple of months/quarterly.

+

Alpha releases

+

The live current state of all the master branches are compiled as an alpha release. This is automatically kept up to date for each successful merging of a PR or PR cluster. +Each alpha release will therefore have a major and minor version number according to the current development cycle, followed by an alpha and an incremented release number for each occurrence,
+i.e. major.minor.α.increment.

+

Beta releases

+

At the end of a successful development cycle a new beta version will be released
+i.e. major.minor.β.0.

+

A new minor development cycle will therefore then start.

+

Hotfixes to beta releases are made only in exceptional circumstances.
+That is if and only if a critical issue is found and it is deemed necessary to include in the previous minor version, in advance of the release of the current cycle. If this happens, the last digit of the beta release will be incremented, i.e. major.minor.β.1 etc.

+

Example development and release sequence

+

Example table of a sequence of releases over a number of development cycles:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
2.12.22.3
2.1.α.0
2.1.α.1
2.1.α.2
2.1.α.3
...
2.1.β.02.2.α.0
2.2.α.1
2.2.α.2
...
2.2.β.02.3.α.0
2.2.β.12.3.α.1
2.3.α.2
...
+

Bold denotes deployed release
+Italic denotes hotfix

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Changelog/index.html b/DevOps/Changelog/index.html new file mode 100644 index 000000000..a924a3f20 --- /dev/null +++ b/DevOps/Changelog/index.html @@ -0,0 +1,4286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM change log - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM change log

+

The change log is made by aggregating the notes from Pull Requests for each repository within the organisation. They are available here

+

Pull requests

+

To simplify the managing of the changelog it is best practice to note what has changed at the time of a pull request. The change log will be generated from the title and body of the pull request using the PULL_REQUEST_TEMPLATE.

+

The Pull Request Title should state, in a simple sentence, what the Pull Request is changing. For toolkits, this should not include the toolkit title, however, for multi-project repositories it should. For example:

+

A Pull Request raised on the XML Toolkit to update Space Type will simply have the title of:

+
+

Update Space Type

+
+

Whereas a Pull Request on the BHoM_Engine to update the Environment Engine panel query will have the title of:

+
+

Environment_Engine: Update panel query to use names

+
+

If the changes are greater than a single sentence can describe, then in the Changelog section, describe the changes in a bulleted list. +The bullet points are required and no other information other than brief definition of changes should be made in this section. The Additional Comments section is then for any additional information or more verbose context.

+

For example:

+
### Changelog
+
+- `Query.Tangent()` Query method added in the `Structure_Engine` for `Bar` class
+
+

The entries made here will be mined for the next release and added to the changelog in one go.

+

Pull requests must also have a label defining their type - either feature, bug fix, test script, documentation, compliance, or other approved type of pull request. This is to aid categorisation of pull requests for the change log. Where a pull request might span multiple types (for example, a pull request adding a new feature and fixing a bug in the same work), then multiple type labels may be applied.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-All/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-All/index.html new file mode 100644 index 000000000..dfb44ee97 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-All/index.html @@ -0,0 +1,4277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check All - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check All

+

This check will trigger all checks available to BHoMBot to be queued for the pull request. BHoMBot will confirm what checks are being triggered when the command is run.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check all

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Branch-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Branch-Compliance/index.html new file mode 100644 index 000000000..9f2045dee --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Branch-Compliance/index.html @@ -0,0 +1,4278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Branch Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Branch Compliance

+

This check will confirm the branch name for the pull request matches the guidelines.

+

If the check is unsuccessful, it will remind you of the conventions for next time. Following the conventions on branch naming is very important for CI processes.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check branch-compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Code-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Code-Compliance/index.html new file mode 100644 index 000000000..7223eb258 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Code-Compliance/index.html @@ -0,0 +1,4279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Code Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Code Compliance

+

This check will confirm the cs files changed within a pull request are compliant to the guidelines for code files. This check will run only the compliance checks that have the Compliance Type of code in the table on the linked page.

+

If the check is unsuccessful, you can trigger BHoMBot to make certain fixes for you. This can be accessed by viewing the details of the check and clicking the Fix button to trigger the process on the pull request.

+

If you believe the check has failed erroneously, you can request dispensation from the CI/CD team. This can be accessed by viewing the details of the check and clicking the Request Dispensation button to trigger the process on the pull request. The CI/CD team will review the failures and weigh up the options on progressing the pull request. Dispensation may not always be granted, but this will be a discussion between the pull request collaborators and the CI/CD team.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check code-compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Compliance/index.html new file mode 100644 index 000000000..e9ec25b24 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Compliance/index.html @@ -0,0 +1,4277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Compliance

+

This check will trigger all compliance checks available to BHoMBot to be queued for the pull request. BHoMBot will confirm what compliance checks are being triggered when the command is run.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Copyright-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Copyright-Compliance/index.html new file mode 100644 index 000000000..b0e8990d0 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Copyright-Compliance/index.html @@ -0,0 +1,4279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Copyright Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Copyright Compliance

+

This check will confirm the cs files changed within a pull request are compliant to the guidelines for having valid copyright on their code files. This check will run only the compliance checks that have the Compliance Type of copyright in the table on the linked page.

+

If the check is unsuccessful, you can trigger BHoMBot to make certain fixes for you. This can be accessed by viewing the details of the check and clicking the Fix button to trigger the process on the pull request.

+

If you believe the check has failed erroneously, you can request dispensation from the CI/CD team. This can be accessed by viewing the details of the check and clicking the Request Dispensation button to trigger the process on the pull request. The CI/CD team will review the failures and weigh up the options on progressing the pull request. Dispensation may not always be granted, but this will be a discussion between the pull request collaborators and the CI/CD team.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check copyright-compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Core/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Core/index.html new file mode 100644 index 000000000..e78c74eb9 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Core/index.html @@ -0,0 +1,4281 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Core - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Core

+

This check will confirm the pull request will compile successfully on its own. This check is operated by both BHoMBot (all repositories) and Azure DevOps (selected repositories).

+

The check will clone the repository associated to the pull request, then clone the repositories listed in that repositories dependencies.txt file and build them in the order listed in that file. The pull request will then be built last.

+

Providing the compilation is successful, the check will return a pass. If the pull request cannot compile then it will return an error. BHoMBot will list the errors as annotations, while Azure needs to be reviewed to ascertain the errors.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check core

+
+

Azure DevOps>/azp run <Your_Toolkit>.CheckCore

+

(where <Your_Toolkit> is the name of your repository).

+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Dataset-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Dataset-Compliance/index.html new file mode 100644 index 000000000..04d12d0c4 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Dataset-Compliance/index.html @@ -0,0 +1,4279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Dataset Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Dataset Compliance

+

This check will confirm the json files changed within a pull request are compliant to the dataset guidelines for dataset files. This check will run only the compliance checks that have the Compliance Type of dataset in the table on the linked page.

+

If the check is unsuccessful, you can trigger BHoMBot to make certain fixes for you. This can be accessed by viewing the details of the check and clicking the Fix button to trigger the process on the pull request.

+

If you believe the check has failed erroneously, you can request dispensation from the CI/CD team. This can be accessed by viewing the details of the check and clicking the Request Dispensation button to trigger the process on the pull request. The CI/CD team will review the failures and weigh up the options on progressing the pull request. Dispensation may not always be granted, but this will be a discussion between the pull request collaborators and the CI/CD team.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check dataset-compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Documentation-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Documentation-Compliance/index.html new file mode 100644 index 000000000..30fd4afe3 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Documentation-Compliance/index.html @@ -0,0 +1,4279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Documentation Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Documentation Compliance

+

This check will confirm the cs files changed within a pull request are compliant to the documentation guidelines for code files. This check will run only the compliance checks that have the Compliance Type of documentation in the table on the linked page.

+

If the check is unsuccessful, you can trigger BHoMBot to make certain fixes for you. This can be accessed by viewing the details of the check and clicking the Fix button to trigger the process on the pull request.

+

If you believe the check has failed erroneously, you can request dispensation from the CI/CD team. This can be accessed by viewing the details of the check and clicking the Request Dispensation button to trigger the process on the pull request. The CI/CD team will review the failures and weigh up the options on progressing the pull request. Dispensation may not always be granted, but this will be a discussion between the pull request collaborators and the CI/CD team.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check documentation-compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Installer/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Installer/index.html new file mode 100644 index 000000000..d4f2811ff --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Installer/index.html @@ -0,0 +1,4304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Installer - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Installer

+

The Check-Installer pipeline answers the question of:

+
+

If this pull request is merged to develop or main, could we build a deployable installer from it?

+
+

This checks all of the repositories included within the BHoM_Installer against the branch of the pull request of the toolkit being checked, and ensures all repositories included within the installer are built successfully. Any problems are then identified early and able to be handled appropriately.

+

If any part of the installer fails to build successfully then a failed check will be returned to the pull request.

+

For BHoMBot, if you have dependant pull requests linked as part of a series, running the check on one pull request will trigger a check result (success or failure depending on the outcome) to all pull requests in the series, as they will have all been tested when requested.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check installer

+
+
+

Arguments

+

-quick - if this flag is provided, then only the code changed by the pull request and its immediate dependencies (upstream) will be compiled. If not provided, the default of compiling all the code in the installer will be used instead.

+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Null-Handling/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Null-Handling/index.html new file mode 100644 index 000000000..9a2c6c854 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Null-Handling/index.html @@ -0,0 +1,4282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Null Handling - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Null Handling

+

This check will confirm the changes proposed by the pull request do not negatively impact the results of Null Handling tests.

+

The check will clone the repository associated to the pull request, and its dependencies listed within the dependencies.txt file, and compile all of them to get the relevant DLLs. Once the DLLs are generated, the Null Handling test will generate against the master branches of those repositories. Following that result, the DLLs will be regenerated against the branch of the pull request and generate a second result to compare with.

+

If the two results come back equal (i.e. there is no change to Null Handling presented by your pull request) then this will report back as a pass.

+

If the errors of your branch report less Null Handling errors than the master result, AND any errors in your branch report exist on the master result, this will be deemed to be an improvement and will report back as a pass.

+

If the errors of your branch are less than those of the master result, but the errors on your branch result do not exist on the master result, this will be deemed to be a failure as your pull request(s) are resulting in new Null Handling errors.

+

If the errors of your branch are more than the errors of the master result then this is also deemed to be a failure as your pull request(s) are increasing the number of Null Handling errors.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check null-handling

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-PR-Builds/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-PR-Builds/index.html new file mode 100644 index 000000000..5bf77f363 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-PR-Builds/index.html @@ -0,0 +1,4215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check-PR-Builds - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check-PR-Builds

+

Trigger Status: This check is automatically triggered when you raise a PR and push commits to that PR.

+

These checks, run by AppVeyor, validate that the current state of the code on the PR builds successfully with all of its own dependencies.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Project-Compliance/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Project-Compliance/index.html new file mode 100644 index 000000000..83a4b0090 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Project-Compliance/index.html @@ -0,0 +1,4278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Project Compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Project Compliance

+

This check will confirm the csproj files changed within a pull request are compliant to the guidelines for project files.

+

If the check is unsuccessful, you can trigger BHoMBot to make the fixes for you. This can be accessed by viewing the details of the check and clicking the Fix button to trigger the process on the pull request.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check project-compliance

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Ready-To-Merge/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Ready-To-Merge/index.html new file mode 100644 index 000000000..9f8bd913b --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Ready-To-Merge/index.html @@ -0,0 +1,4285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Ready To Merge - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Ready To Merge

+

This check will confirm the pull request is ready to merge based on the following conditions.

+
    +
  • Any requested changes have been addressed (changed to an approving review) or dismissed
  • +
  • The pull request does not have a status:do-not-merge label
  • +
  • The pull request has suitable labels for the change log - labels should be starting with type: to denote the type of pull request
  • +
  • The pull request has at least one approving review
  • +
  • The pull request has passed check-core and check-installer from BHoMBot
  • +
+

This check is done for all pull requests that are linked in a series. If any of the pull requests are not ready, then the check will report that none of them are ready. This is to protect against merging pull requests in a series that may be dependent on each other accidently, where one pull request is ready to merge but another is not. This protects the installer builds (where check-installer reports a pass to all pull requests because the changes are ok, but if one of the pull requests then isn't merged it will fail to build the installer later) as well.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check ready-to-merge

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Required/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Required/index.html new file mode 100644 index 000000000..4462b84f7 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Required/index.html @@ -0,0 +1,4277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Required - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Required

+

This check will trigger all required checks available to BHoMBot to be queued for the pull request. BHoMBot will confirm what required checks are being triggered when the command is run.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check required

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Serialisation/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Serialisation/index.html new file mode 100644 index 000000000..b8aaceafd --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Serialisation/index.html @@ -0,0 +1,4282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Serialisation - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Serialisation

+

This check will confirm the changes proposed by the pull request do not negatively impact the results of serialisation tests.

+

The check will clone the repository associated to the pull request, and its dependencies listed within the dependencies.txt file, and compile all of them to get the relevant DLLs. Once the DLLs are generated, the serialisation test will generate against the master branches of those repositories. Following that result, the DLLs will be regenerated against the branch of the pull request and generate a second result to compare with.

+

If the two results come back equal (i.e. there is no change to serialisation presented by your pull request) then this will report back as a pass.

+

If the errors of your branch report less serialisation errors than the master result, AND any errors in your branch report exist on the master result, this will be deemed to be an improvement and will report back as a pass.

+

If the errors of your branch are less than those of the master result, but the errors on your branch result do not exist on the master result, this will be deemed to be a failure as your pull request(s) are resulting in new serialisation errors.

+

If the errors of your branch are more than the errors of the master result then this is also deemed to be a failure as your pull request(s) are increasing the number of serialisation errors.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check serialisation

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Unit-Tests/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Unit-Tests/index.html new file mode 100644 index 000000000..2fd3246ce --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Unit-Tests/index.html @@ -0,0 +1,4282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Unit Tests - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Unit Tests

+

This check will confirm the unit tests set up within a .ci/Datasets folder on a repository run successfully using the Unit Test framework.

+

The check will clone the repository associated to the pull request, and its dependencies listed within the dependencies.txt file, and compile all of them to get the relevant DLLs. Once the DLLs are generated, the unit tests will then run and compare the serialised results against the results coming out from the pull request.

+

The result of a unit test check may require further investigation and interpretation by a human reviewer.

+

If the check passes, then the unit tests serialised and the results from the pull request match exactly.

+

If the check fails, then it means the check found differences between the serialised results, and the new results. This is where investigation may be needed, as some differences may be failures (where the pull request is negatively impacting the result), but some differences may be improvements (where the pull request is making outcomes better compared to the serialised results which are made against a version of master that the toolkit leads are happy with).

+

If the check fails, but is providing better results and a human review agrees that the pull request is improving the standard, then it is recommended to update the unit tests against master after merging the pull request as soon as possible to ensure that version of results are stored for future pull requests. Unit tests can be updated on the pull request itself if agreed by the toolkit lead.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check unit-tests

+
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/CI Checks/Check-Versioning/index.html b/DevOps/Code Compliance and CI/CI Checks/Check-Versioning/index.html new file mode 100644 index 000000000..4caef7332 --- /dev/null +++ b/DevOps/Code Compliance and CI/CI Checks/Check-Versioning/index.html @@ -0,0 +1,4304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Check Versioning - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Check Versioning

+

This check will confirm the changes proposed by the pull request do not negatively impact the results of versioning tests.

+

The check will clone all the repositories in the BHoM_Installer and compile all of them to get the relevant DLLs. Once the DLLs are generated, the versioning test will generate against the master branches of those repositories. Following that result, the DLLs will be regenerated against the branch of the pull request and generate a second result to compare with.

+

If the two results come back equal (i.e. there is no change to versioning presented by your pull request) then this will report back as a pass.

+

If the errors of your branch report less versioning errors than the master result, AND any errors in your branch report exist on the master result, this will be deemed to be an improvement and will report back as a pass.

+

If the errors of your branch are less than those of the master result, but the errors on your branch result do not exist on the master result, this will be deemed to be a failure as your pull request(s) are resulting in new versioning errors.

+

If the errors of your branch are more than the errors of the master result then this is also deemed to be a failure as your pull request(s) are increasing the number of versioning errors.

+
+

Trigger commands:

+

BHoMBot

+
+

@BHoMBot check versioning

+
+
+

Arguments

+

-force - if provided, this will force the versioning check to run even if it could be bypassed. If no .cs files have been changed by the pull request, it will bypass the versioning check to save time (as only changes to .cs files typically introduce versioning issues). Use this flag to force versioning to be checked regardless of whether .cs files have changed.

+

-quick - if provided, this will only compile the code in the request pull request and any immediate upstream dependencies. Without this flag, all the code in the installer is compiled to then check versioning against.

+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/AssemblyInfo-compliance/index.html b/DevOps/Code Compliance and CI/Compliance Checks/AssemblyInfo-compliance/index.html new file mode 100644 index 000000000..2c1f1719b --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/AssemblyInfo-compliance/index.html @@ -0,0 +1,4328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + AssemblyInfo compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

AssemblyInfo compliance

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

Assembly Information

+

This section is only valid for projects utilising the old-style CSProject files, where an AssemblyInfo.cs file is present. If an AssemblyInfo.cs file is not present, then the compliance of this information can be found here.

+

Each DLL should have suitable assembly information to support automated processes and confirming the version of the code which the DLL was built against. This includes these three items:

+
    +
  • <AssemblyVersion>
  • +
  • <AssemblyFileVersion>
  • +
  • <AssemblyDescription>
  • +
+

The AssemblyVersion should be set to the major version for the annual development cycle. This is set by DevOps, and will typically be a 4-digit number where the first number is the major version for the year, followed by three 0's - e.g. 5.0.0.0 for the 2022 development calendar (note, development calendars are based on release schedules as outlined by DevOps, not any other calendar system).

+

The AssemblyFileVersion should be set to the current development milestone, which is the major version followed by the milestone, followed by two 0's - e.g. 5.3.0.0 for the development milestone running from June-September 2022.

+

The AssemblyDescription attribute should contain the full link to the repository where the DLL is stored, e.g. https://github.com/BHoM/Test_Toolkit for DLLs where the code resides in Test_Toolkit.

+

At the start of each milestone, BHoMBot will automatically uptick the AssemblyVersion and AssemblyFileVersion as appropriate, and set the AssemblyDescription if it was not previously set. However, if you add a new project during a milestone, BHoMBot will flag these items as incompliant if they have not been resolved prior to running the project-compliance check. These items can be fixed by BHoMBot if you request BHoMBot to fix the project information.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/AttributeHasEndingPunctuation/index.html b/DevOps/Code Compliance and CI/Compliance Checks/AttributeHasEndingPunctuation/index.html new file mode 100644 index 000000000..c3f8df4ca --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/AttributeHasEndingPunctuation/index.html @@ -0,0 +1,4288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + AttributeHasEndingPunctuation - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

AttributeHasEndingPunctuation

+ +

Summary

+

Severity - Warning

+

Check method - Here

+

Details

+

The AttributeHasEndingPunctuation check ensures that an attribute providing documentation (input, description output or multioutput) ends with a suitable piece of punctuation. See the check method for the current accepted list.

+

This check is useful for helping provide delineation between the documentation you provide as the developer, and the documentation provided automatically on components within the UI.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/EngineClassMatchesFilePath/index.html b/DevOps/Code Compliance and CI/Compliance Checks/EngineClassMatchesFilePath/index.html new file mode 100644 index 000000000..a27692fad --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/EngineClassMatchesFilePath/index.html @@ -0,0 +1,4289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + EngineClassMatchesFilePath - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

EngineClassMatchesFilePath

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The EngineClassMatchesFilePath check looks at whether the the class of the engine method matches based on its file path.

+

For example, Compute class files should sit within the file path Your_Toolkit/Toolkit_Engine/Compute and not within Your_Toolkit/Toolkit_Engine/Query. This check ensures the class name is correct based on the file name.

+

Files contained within an Engines Objects folder are exempt from this check (e.g. files with the file path Your_Toolkit/Toolkit_Engine/Objects/Foo.cs will be exempt).

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasConstructor/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasConstructor/index.html new file mode 100644 index 000000000..ccc1202c3 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasConstructor/index.html @@ -0,0 +1,4325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasConstructor - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasConstructor

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasConstructor check ensures that all BHoM objects do not have a constructor unless they are implementing the IImmutable interface on the object.

+

Constructors are only valid on IImmutable objects that contain get only properties, and are necessary for BHoM serialisation to function correctly.

+

The following scenarios will result in this check failing:

+
    +
  • An object which contains a constructor, and does not implement the IImmutable interface
  • +
  • An object which implements the IImmutable interface, but does not contain a constructor
  • +
+

More information

+

More information on the use of IImmutable interface within the BHoM can be found here.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasDescriptionAttribute/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasDescriptionAttribute/index.html new file mode 100644 index 000000000..6d3d1a7f0 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasDescriptionAttribute/index.html @@ -0,0 +1,4332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasDescriptionAttribute - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasDescriptionAttribute

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasDescriptionAttribute check ensures that a method has a Description attribute explaining what the method is doing for users.

+

You can add a Description attribute with the following syntax sitting above the method:

+

[Description("Your description here")]

+

If you have not used any attributes in your file previously, you may need to add the following usings:

+

using BH.oM.Base.Attributes;

+

using System.ComponentModel;

+
+

Description authoring guidelines

+

We should be aiming for all properties, objects and methods to have a description. With only the very simplest of self explanatory properties to not require a description by exception - and indeed only where the below guidelines can not be reasonably satisfied.

+

So what makes a good description?

+
    +
  1. A description must impart additional useful information beyond the property name, object and namespace.
  2. +
  3. Further to a definition, the description is an opportunity to include usage guidance, tips or additional context.
  4. +
  5. The description is a place you can include synonyms etc. to help clarify for others in different regions/domains, being inclusive as possible.
  6. +
  7. Also don't forget the addition of a Quantity Attribute can be used now, appropriate for Doubles and Vectors.
  8. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasOneConstructor/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasOneConstructor/index.html new file mode 100644 index 000000000..7549a6fdd --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasOneConstructor/index.html @@ -0,0 +1,4321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasOneConstructor - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasOneConstructor

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasOneConstructor check ensures that all BHoM objects that do have a constructor (and are allowed to do so by implementing the IImmutable interface) only contains one constructor with parameters.

+

Objects which implement a constructor are permitted to also implement a parameterless constructor, but only if this is necessary.

+

Objects which implement more than one constructor taking parameters will be flagged as failing this check.

+

More information

+

More information on the use of IImmutable interface within the BHoM can be found here.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasOutputAttribute/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasOutputAttribute/index.html new file mode 100644 index 000000000..041f6148c --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasOutputAttribute/index.html @@ -0,0 +1,4292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasOutputAttribute - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasOutputAttribute

+ +

Summary

+

Severity - Warning

+

Check method - Here

+

Details

+

The HasOutputAttribute check ensures that a method has a Output or MultiOutput attribute explaining what the method is providing for users.

+

You can add an Output attribute with the following syntax sitting above the method:

+

[Output("outputName", "Your description here")]

+

If you have not used any attributes in your file previously, you may need to add the following using:

+

using BH.oM.Reflection.Attributes;

+

You may also need to add a reference to the Reflection_oM to your project if you have not previously used it.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasPublicGet/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasPublicGet/index.html new file mode 100644 index 000000000..c2029067a --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasPublicGet/index.html @@ -0,0 +1,4292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasPublicGet - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasPublicGet

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasPublicGet check ensures that object properties have public get accessors. A property of a BHoM object which does not have a public get accessor will fail this check.

+

For example, the following object definition will fail this check, because the get accessor does not exist.

+

public double MyDouble { set; }

+

This property will pass as a compliant property.

+

public double MyDouble { get; set; } = 0.0

+

This check is only operating on oM based objects. Objects within an Objects folder of an Engine (Engine/Objects) or Adapters are exempt from this check.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasSingleClass/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasSingleClass/index.html new file mode 100644 index 000000000..ca3f69c56 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasSingleClass/index.html @@ -0,0 +1,4299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasSingleClass - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasSingleClass

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasSingleClass check ensures there is only one class declaration per cs file. This is designed to make the code easy to find and understand by people coming into BHoM for the first time.

+

For example, a class which looks like the below, would be invalid and fail this check. There should only be one class declaration per file.

+
namesapce BH.Engine.Test
+{
+    public static partial class Query
+    {
+    }
+
+    public static partial class Compute
+    {
+    }
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasSingleNamespace/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasSingleNamespace/index.html new file mode 100644 index 000000000..7eaa37869 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasSingleNamespace/index.html @@ -0,0 +1,4296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasSingleNamespace - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasSingleNamespace

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasSingleNamespace check makes sure only one namespace is declared in a given file.

+

For example, the file below would fail because it is declaring two namespaces within the file.

+
namespace BH.Engine.Test
+{
+}
+
+namespace BH.Engine.Environment
+{
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasUniqueMultiOutputAttributes/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasUniqueMultiOutputAttributes/index.html new file mode 100644 index 000000000..0842934e1 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasUniqueMultiOutputAttributes/index.html @@ -0,0 +1,4303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasUniqueMultiOutputAttributes - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasUniqueMultiOutputAttributes

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasUniqueMultiOutputAttributes check ensures that a method returning a type of Output<t, ..., tn> has a matching number of MultiOutput attributes that have unique indexes.

+

For example, a method returning Output<Panel, Opening> would require 2 uniquely indexed MultiOutput attributes to document both the Panel and the Opening.

+

If the method looked like the below, while containing 2 MultiOutput attributes, would fail this check, because the index for both outputs cannot be 0.

+
[MultiOutput(0, "panel")]
+[MultiOutput(0, "opening")]
+public static Output<Panel, Opening> MyTestMethod()
+{
+}
+
+

The method should instead look like this:

+
[MultiOutput(0, "panel")]
+[MultiOutput(1, "opening")]
+public static Output<Panel, Opening> MyTestMethod()
+{
+}
+
+

Where the index of the MultiOutput attributes is unique.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasUniqueOutputAttribute/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasUniqueOutputAttribute/index.html new file mode 100644 index 000000000..67b29056c --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasUniqueOutputAttribute/index.html @@ -0,0 +1,4287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasUniqueOutputAttribute - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasUniqueOutputAttribute

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasUniqueOutputAttribute check ensures that there is only one Output or MultiOutput attribute per method. This is to avoid confusion caused by multiple Output or MultiOutput attributes unnecessarily.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasValidConstructor/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasValidConstructor/index.html new file mode 100644 index 000000000..abf4765f9 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasValidConstructor/index.html @@ -0,0 +1,4400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasValidConstructor - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasValidConstructor

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasValidConstructor check ensures that any BHoM object which implements a constructor with parameters contains all of the parameters it requires to satisfy the Serialisation requirement.

+

Constructors should only exist on objects implementing the IImmutable interface. Objects with this interface should have properties which are get only (no set accessor). All of these get only properties should be parameters to the constructor, with the parameter name matching the property name following the usual lowercase conventions for parameter names.

+

Consider the following IImmutable object, which does not have a constructor.

+
public class MyObject : BHoMObject, IImmutable
+{
+    public virtual int MyInt { get; }
+    public virtual string MyString { get; }
+    public virtual Point MyPoint { get; set; }
+}
+
+

This object will not correctly deserialise, as it will not be able to adequately set the properties MyInt and MyString. Therefore, a constructor must be provided with the parameter names matching, so the deserialisation can correctly align the deserialised data to the object property.

+

The property MyPoint does not have to be a parameter to the constructor, as it implements a set accessor. This is true for any property, including those inherited from the base BHoMObject.

+

As such, a valid constructor would look like this:

+
public MyObject(int myInt, string myString)
+{
+    //Constructor logic
+}
+
+

Example of a valid object

+

The entire class, in its valid form, would look like this:

+
public class MyObject : BHoMObject, IImmutable
+{
+    public virtual int MyInt { get; }
+    public virtual string MyString { get; }
+    public virtual Point MyPoint { get; set; }
+
+    public MyObject(int myInt, string myString)
+    {
+        //Constructor logic
+    }
+}
+
+

Example of an invalid object

+

If the constructor does not contain input parameters for all of the properties which implement only the get accessor, this will flag as a failure under this check. The following object is therefore incompliant, as only MyInt has a matching input parameter:

+
public class MyObject : BHoMObject, IImmutable
+{
+    public virtual int MyInt { get; }
+    public virtual string MyString { get; }
+    public virtual Point MyPoint { get; set; }
+
+    public MyObject(int myInt)
+    {
+        //Constructor logic
+    }
+}
+
+

More information

+

More information on the use of IImmutable interface within the BHoM can be found here.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasValidCopyright/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasValidCopyright/index.html new file mode 100644 index 000000000..f75c90852 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasValidCopyright/index.html @@ -0,0 +1,4288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasValidCopyright - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasValidCopyright

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasValidCopyright check ensures that all BHoM files licenced under LGPL v3.0 contain the correct copyright statement as their header.

+

The BHoM copyright statement for BHoM files licenced under LGPL v3.0 can be found here.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasValidMultiOutputAttributes/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasValidMultiOutputAttributes/index.html new file mode 100644 index 000000000..7c9086ce3 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasValidMultiOutputAttributes/index.html @@ -0,0 +1,4288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasValidMultiOutputAttributes - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasValidMultiOutputAttributes

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasValidMultiOutputAttributes check ensures that a method returning a type of Output<t, ..., tn> has a matching number of MultiOutput attributes documenting the returned objects.

+

For example, a method returning Output<Panel, Opening> would require 2 MultiOutput attributes to document both the Panel and the Opening.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasValidOutputAttribute/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasValidOutputAttribute/index.html new file mode 100644 index 000000000..333de4d90 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasValidOutputAttribute/index.html @@ -0,0 +1,4314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasValidOutputAttribute - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasValidOutputAttribute

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasValidOutputAttribute check ensures that, if there is a piece of Output documentation is present on a method, that it is of a correct type.

+

MultiOutput documentation should only be used on methods providing multiple outputs using the return type of Output<t1, t2, ..., tn>, while Output documentation should be present on methods returning a single type.

+

For example, the following two methods will fail this check because the documentation does not match the return types.

+
[Output("outputVariable", "My output documentation")]
+public static Output<bool, string> MyOutputMethod()
+{
+
+}
+
+
[MultiOutput(0, "outputVariable", "My output documentation")]
+public static bool MyOutputMethod()
+{
+
+}
+
+

These methods fail this check because the MultiOutput documentation is on a method returning a single type, while the Output documentation is on a method returning multiple results. For these methods to pass this check, they should look like this:

+
[MultiOutput(0, "outputVariable", "My output documentation")]
+public static Output<bool, string> MyOutputMethod()
+{
+
+}
+
+
[Output("outputVariable", "My output documentation")]
+public static bool MyOutputMethod()
+{
+
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HasValidPreviousVersionAttribute/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HasValidPreviousVersionAttribute/index.html new file mode 100644 index 000000000..5668dabb5 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HasValidPreviousVersionAttribute/index.html @@ -0,0 +1,4290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HasValidPreviousVersionAttribute - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HasValidPreviousVersionAttribute

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The HasValidPreviousVersionAttribute check ensures that, if there is a piece of versioning documentation present explaining what the previous version of a method or constructor was, the FromVersion is correct.

+

The FromVersion for a PreviousVersion attribute should be set to the current milestone of development, with PreviousVersion attributes being removed at the end of the milestone in preparation for the next.

+

If a PreviousVersion attribute has not been tidied up, it will be flagged by this check and should be removed in the Pull Request which captures it.

+

If a PreviousVersion attribute has been added in that Pull Request, the FromVersion should match the current development milestone cycle.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/HiddenInputsAreLast/index.html b/DevOps/Code Compliance and CI/Compliance Checks/HiddenInputsAreLast/index.html new file mode 100644 index 000000000..610b120b9 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/HiddenInputsAreLast/index.html @@ -0,0 +1,4300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HiddenInputsAreLast - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

HiddenInputsAreLast

+ +

Summary

+

Severity - Warning

+

Check method - Here

+

Details

+

This check ensures that if you have set any Input attributes to have UIExposure.Hidden, they are the last parameters in the list of the method.

+

This is because inputs which are being hidden from the UI are likely to be of a lower priority than those being displayed, and should not get higher precedence in the method signature, particularly when displaying the method to users.

+

This is however just a warning, and final say will rest with the relevant maintainers of the repository.

+

An example of the check failing is given below.

+
[Input("environmentObject", "Any object implementing the IEnvironmentObject interface that can have its tilt queried.")]
+[Input("distanceTolerance", "Distance tolerance for calculating discontinuity points, default is set to BH.oM.Geometry.Tolerance.Distance.", UIExposure.Hidden)]
+[Input("angleTolerance", "Angle tolerance for calculating discontinuity points, default is set to the value defined by BH.oM.Geometry.Tolerance.Angle.")]
+public static double SomeMethod(this IEnvironmentObject environmentObject, double distanceTolerance = BH.oM.Geometry.Tolerance.Distance, double angleTolerance = BH.oM.Geometry.Tolerance.Angle)
+{
+    return 0.0;
+}
+
+

In this example, the second Input attribute for distanceTolerance is setting the UIExposure to be Hidden, but the third method parameter, angleTolerance, does not have the same UIExposure (the default being Display). This would flag with this compliance check.

+

To correct this, we can either set angleTolerance to also have a UIExposure.Hidden, or change the tolerances around so that angleTolerance comes before distanceTolerance in the argument list.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/InputAttributeHasMatchingParameter/index.html b/DevOps/Code Compliance and CI/Compliance Checks/InputAttributeHasMatchingParameter/index.html new file mode 100644 index 000000000..ed73c0750 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/InputAttributeHasMatchingParameter/index.html @@ -0,0 +1,4314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + InputAttributeHasMatchingParameter - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

InputAttributeHasMatchingParameter

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The InputAttributeHasMatchingParameter check ensures that a given Input or InputFromProperty attribute has a matching input parameter on a method.

+

This ensures that our documentation is accurate and valid for what users might see.

+

For example, the following methods would fail this check because the input attribute does not match a given input parameter.

+
[Input("hello", "My variable")]
+public static void HelloWorld(double goodbye)
+{
+
+}
+
+
[InputFromProperty("hello")]
+public static void HelloWorld(double goodbye)
+{
+
+}
+
+

The correct implementation should instead look like this:

+
[Input("hello", "My variable")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+
[InputFromProperty("hello")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/InputAttributeIsUnique/index.html b/DevOps/Code Compliance and CI/Compliance Checks/InputAttributeIsUnique/index.html new file mode 100644 index 000000000..140e1c716 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/InputAttributeIsUnique/index.html @@ -0,0 +1,4317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + InputAttributeIsUnique - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

InputAttributeIsUnique

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The InputAttributeIsUnique check ensures that there are not duplicate Input or InputFromProperty attributes for the same parameter.

+

This ensures that our documentation is accurate and valid for what users might see.

+

For example, the following methods would fail this check because the input attribute is duplicated

+
[Input("hello", "My variable")]
+[Input("hello", "Also my variable")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+
[Input("hello", "My variable")]
+[InputFromProperty("hello")]
+public static void HelloWorld(double hello, double goodbye)
+{
+
+}
+
+

The correct implementation should instead look like this:

+
[Input("hello", "My variable")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+
[Input("hello", "My variable")]
+[InputFromProperty("goodbye")]
+public static void HelloWorld(double hello, double goodbye)
+{
+
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/InputAttributesAreInOrder/index.html b/DevOps/Code Compliance and CI/Compliance Checks/InputAttributesAreInOrder/index.html new file mode 100644 index 000000000..4e7acdace --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/InputAttributesAreInOrder/index.html @@ -0,0 +1,4318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + InputAttributesAreInOrder - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

InputAttributesAreInOrder

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The InputAttributesAreInOrder check ensures that any Input or InputFromProperty attributes are in the same order as the input parameters for the given method.

+

This ensures that our documentation is easy to follow for developets.

+

For example, the following methods would fail this check because the input attribute not in the same order as the method input parameters.

+
[Input("hello", "My variable")]
+[Input("goodbye", "Also my variable")]
+public static void HelloWorld(int goodbye, double hello)
+{
+
+}
+
+
[Input("goodbye", "My variable")]
+[InputFromProperty("hello")]
+public static void HelloWorld(double hello, double goodbye)
+{
+
+}
+
+

The correct implementation should instead look like this:

+
[Input("goodbye", "My variable")]
+[Input("hello", "Also my variable")]
+public static void HelloWorld(int goodbye, double hello)
+{
+
+}
+
+
[InputFromProperty("hello")]
+[Input("goodbye", "My variable")]
+public static void HelloWorld(double hello, double goodbye)
+{
+
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/InputParameterStartsLower/index.html b/DevOps/Code Compliance and CI/Compliance Checks/InputParameterStartsLower/index.html new file mode 100644 index 000000000..3a94084ac --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/InputParameterStartsLower/index.html @@ -0,0 +1,4291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + InputParameterStartsLower - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

InputParameterStartsLower

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The InputParameterStartsLower check ensures that method input variables (parameters) start with a lowercase letter.

+

This example would fail this check, because the variable name starts with an uppercase character.

+

public static void HelloWorld(double Hello)

+

While this example will pass because the variable name starts with a lowercase character.

+

public static void HelloWorld(double hello)

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsDocumentationURLValid/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsDocumentationURLValid/index.html new file mode 100644 index 000000000..5cc73f3fa --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsDocumentationURLValid/index.html @@ -0,0 +1,4289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsDocumentationURLValid - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsDocumentationURLValid

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsDocumentationURLValid check ensures that, if there is a documentation URL attribute on the code, that the URL provided can link to a valid web resource.

+

If the check cannot load the URL (returning anything other than a 200 HTTP status code), this will return a fail. If the server is unavailable then this will return a fail and the check may need rerunning if external server availability affects the check.

+

This check does not check the validity of the resource, only that the link provided can be used to access a valid web resource.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsExtensionMethod/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsExtensionMethod/index.html new file mode 100644 index 000000000..4b16a4c54 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsExtensionMethod/index.html @@ -0,0 +1,4301 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsExtensionMethod - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsExtensionMethod

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsExtensionMethod check makes sure that an engine method within a query, modify, or convert class is classed as an extension method to the first object type. Extension methods are made by using the this keyword prior to the declaration of the first input parameter. If a method does not take any inputs to operate, then it is exempt from this check.

+

For example, the following method declaration will fail this check, because it is missing the this keyword before the first object:

+
public static bool MethodIsValid(Panel myPanel, Opening myOpening)
+{
+    return false;
+}
+
+

Whereas this method will pass the check, because the first parameter contains the this keyword to make the method an extension method.

+
public static bool MethodIsValid(this Panel myPanel, Opening myOpening)
+{
+    return false;
+}
+
+

Methods within the Compute and Create classes are exempt from this check.

+

Files contained within an Engines Objects folder are exempt from this check (e.g. files with the file path Your_Toolkit/Toolkit_Engine/Objects/Foo.cs will be exempt).

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsInputAttributePresent/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsInputAttributePresent/index.html new file mode 100644 index 000000000..be3390ded --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsInputAttributePresent/index.html @@ -0,0 +1,4296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsInputAttributePresent - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsInputAttributePresent

+ +

Summary

+

Severity - Warning

+

Check method - Here

+

Details

+

The IsInputAttributePresent check ensures that an input parameter has a matching Input or InputFromProperty attribute explaining what the input is required for users.

+

You can add an Input attribute with the following syntax sitting above the method:

+

[Input("variableName", "Your description here")]

+

Alternatively, if the methods returning object has a property which contains a description which matches the input parameter, you can use the InputFromProperty attribute with the following syntax:

+

[InputFromProperty("variableName")]

+

Or, if your methods returning object has a property which contains a description which matches the input parameter, but the variable name entering the method is not named the same as the object's property, you can use the InputFromProperty to match the two, like so:

+

[InputFromProperty("variableName", "objectPropertyName")]

+

If you have not used any attributes in your file previously, you may need to add the following using:

+

using BH.oM.Reflection.Attributes;

+

You may also need to add a reference to the Reflection_oM to your project if you have not previously used it.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsPublicClass/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsPublicClass/index.html new file mode 100644 index 000000000..ee4ae777e --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsPublicClass/index.html @@ -0,0 +1,4296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsPublicClass - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsPublicClass

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsPublicClass check ensures that classes declared within files have the public modifier, rather than private or internal, etc.

+

The following class declaration would fail because it does not give the public modifier.

+
namespace BH.Engine.Test
+{
+    static partial class Query
+    {
+    }
+}
+
+

Files contained within an Engines Objects folder are exempt from this check (e.g. files with the file path Your_Toolkit/Toolkit_Engine/Objects/Foo.cs will be exempt).

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsPublicProperty/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsPublicProperty/index.html new file mode 100644 index 000000000..18a62423b --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsPublicProperty/index.html @@ -0,0 +1,4291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsPublicProperty - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsPublicProperty

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsPublicProperty check ensures that object properties are public using the public modifier.

+

The follow object property would fail this check because the modifier is set to private.

+

private double MyDouble { get; set; } = 0.1;

+

All BHoM object properties should be publicly accessible.

+

This check is only operating on oM based objects. Objects within an Objects folder of an Engine (Engine/Objects) or Adapters are exempt from this check.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsStaticClass/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsStaticClass/index.html new file mode 100644 index 000000000..7d08e4216 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsStaticClass/index.html @@ -0,0 +1,4296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsStaticClass - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsStaticClass

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsStaticClass check ensures class declarations contain the static modifier.

+

The following class declaration would fail because it does not give the static modifier.

+
namespace BH.Engine.Test
+{
+    public partial class Query
+    {
+    }
+}
+
+

Files contained within an Engines Objects folder are exempt from this check (e.g. files with the file path Your_Toolkit/Toolkit_Engine/Objects/Foo.cs will be exempt).

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsUsingCustomData/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsUsingCustomData/index.html new file mode 100644 index 000000000..1090d7db0 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsUsingCustomData/index.html @@ -0,0 +1,4290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsUsingCustomData - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsUsingCustomData

+ +

Summary

+

Severity - Warning

+

Check method - Here

+

Details

+

The IsUsingCustomData check highlights whether code written within the BHoM is utilising in any capacity the CustomData variable associated with all BHoMObjects.

+

CustomData is available for volatile data, useful for users within a Visual Programming environment to append data to an object that the object can carry around. However, this data is not designed to be relied upon within the code of toolkits or engines themselves.

+

The use of Fragments is preferred for storing data being pulled from an external source, and would be the most appropriate replacement for CustomData in most instances of the code base. Some exceptions to this do occur however, and are treated on a case-by-case basis by the governance and CI/CD teams. It is advised to avoid using CustomData where ever possible in the first instance.

+

More information on the reasons behind this can be found on this issue documenting the discussion behind this.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsValidConvertMethodName/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsValidConvertMethodName/index.html new file mode 100644 index 000000000..4eae5d878 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsValidConvertMethodName/index.html @@ -0,0 +1,4324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsValidConvertMethodName - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsValidConvertMethodName

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsValidConvertMethodName check ensures that Convert class methods are named correctly based on the guidance for BHoM development.

+

The guidance, at the time of writing, states that Convert methods should go To their external software, and From their external software, rather than ToBHoM or FromBHoM.

+

For example, this Convert method will fail:

+

public static Span ToBHoM()

+

While this one will pass:

+

public static Span ToSoftware()

+

Naming conventions

+

Although not a strict requirement, it is advised that convert method names reflect the software that the convert is going to or from. This helps make it clear what the external object model is and helps inform users of what to expect when using the convert method.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsValidCreateMethod/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsValidCreateMethod/index.html new file mode 100644 index 000000000..31824b742 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsValidCreateMethod/index.html @@ -0,0 +1,4296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsValidCreateMethod - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsValidCreateMethod

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

A create method name should meet the following conditions:

+
    +
  • If the return type matches the method name, the method name must match the filename and sit within the create folder (without any subfolders)
  • +
  • e.g. a Panel object can sit within a file with the structure Engine/Create/Panel.cs in a method called Panel
  • +
+

If the above cannot be done, then: + - A sub-folder should be created which matches the return type, the method name must match the file name, and the method name should partially match the return type + - e.g. a Panel object can sit within a file with the structure Engine/Create/Panel/EnvironmentPanel.cs in a method called EnvironmentPanel + - A level of grouping/nesting is permitted when using the second option to help group create methods appropriately. This nesting is permitted up to two levels before it would become incompliant with the guidelines. + - e.g. a Panel object can fit within a file with the structure Engine/Create/PlanarPanels/Panel/EnvironmentPanel.cs or Engine/Create/Panel/PlanarPanels/EnvironmentPanel.cs - here we group the panels by PlanarPanels. Either option is compliant for the check to pass. Any further folders would however be incompliant.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsValidCreateMethodName/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsValidCreateMethodName/index.html new file mode 100644 index 000000000..88123ae63 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsValidCreateMethodName/index.html @@ -0,0 +1,4287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsValidCreateMethodName - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsValidCreateMethodName

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

This check is related to IsValidCreateMethod, however, this check ensures the method name matches the file name exactly.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsValidDataset/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsValidDataset/index.html new file mode 100644 index 000000000..205921796 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsValidDataset/index.html @@ -0,0 +1,4326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsValidDataset - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsValidDataset

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

Datasets should be stored as valid BHoM JSON objects within a Dataset folder of a repository/toolkit. Dataset files should contain only one serialised dataset object (from BH.oM.Data.Library.Dataset ).

+

This test will take the JSON file and attempt to deserialise it back to a Dataset object. If the deserialisation fails, the error will be reported.

+

Warnings

+

The check will also interrogate the source information for the dataset and ensure:

+
    +
  • That source information exists
  • +
  • That an author has been provided for the source
  • +
  • That a title has been provided for the source
  • +
+

If any of these conditions is not met, a warning will be returned. A warning will not prohibit the Pull Request from being merged, but it may be prudent to address the issues to provide confidence in the source of the dataset.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsValidEngineClassName/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsValidEngineClassName/index.html new file mode 100644 index 000000000..53d5289a9 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsValidEngineClassName/index.html @@ -0,0 +1,4288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsValidEngineClassName - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsValidEngineClassName

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsValidEngineClassName check ensures that any engine class is one of either Create, Compute, Convert, Modify, Query. Any engine file which does not create one of these classes will fail this check.

+

Classes within the Objects folder of engines are not checked against this criteria.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsValidIImmutableObject/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsValidIImmutableObject/index.html new file mode 100644 index 000000000..1972a6d4c --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsValidIImmutableObject/index.html @@ -0,0 +1,4320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsValidIImmutableObject - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsValidIImmutableObject

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsValidIImmutableObject check ensures that IImmutable objects contain at least one property which has only a get accessor (no set accessor).

+

If an object has no properties which are get only, then the IImmutable interface should not be used.

+

More information

+

More information on the use of IImmutable interface within the BHoM can be found here.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/IsVirtualProperty/index.html b/DevOps/Code Compliance and CI/Compliance Checks/IsVirtualProperty/index.html new file mode 100644 index 000000000..59ae0b47c --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/IsVirtualProperty/index.html @@ -0,0 +1,4293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + IsVirtualProperty - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

IsVirtualProperty

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The IsVirtualProperty check ensures that object properties are using the virtual modifier.

+

The follow object property would fail this check because the virtual modifier does not exist.

+

public double MyDouble { get; set; } = 0.1;

+

This property would pass this check because the virtual modifier has been set.

+

public virtual MyDouble { get; set;} = 0.1;

+

All BHoM object properties should be virtual to allow for easy extension.

+

This check is only operating on oM based objects. Objects within an Objects folder of an Engine (Engine/Objects) or Adapters are exempt from this check.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/MethodNameContainsFileName/index.html b/DevOps/Code Compliance and CI/Compliance Checks/MethodNameContainsFileName/index.html new file mode 100644 index 000000000..bb621182f --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/MethodNameContainsFileName/index.html @@ -0,0 +1,4288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + MethodNameContainsFileName - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

MethodNameContainsFileName

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The MethodNameContainsFileName check ensures that method names within Engine files (with the exception of Create methods) at least partially match the file name.

+

For example, a method BHoMTypeList() can exist inside a file TypeList.cs, because the method name contains the file name. However, BHoMTypeCollection() would not be valid as TypeList.cs is not contained within the method name.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/MethodNameStartsUpper/index.html b/DevOps/Code Compliance and CI/Compliance Checks/MethodNameStartsUpper/index.html new file mode 100644 index 000000000..df67fee1f --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/MethodNameStartsUpper/index.html @@ -0,0 +1,4291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + MethodNameStartsUpper - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

MethodNameStartsUpper

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The MethodNameStartsUpper check ensures that method declarations start with an uppercase character.

+

For example, the following method declaration would fail this check because the method name begins with a lowercase character.

+

public static void helloWorld()

+

While this one will pass because it starts with an uppercase character.

+

public static void HelloWorld()

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/ModifyReturnsDifferentType/index.html b/DevOps/Code Compliance and CI/Compliance Checks/ModifyReturnsDifferentType/index.html new file mode 100644 index 000000000..d155392f1 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/ModifyReturnsDifferentType/index.html @@ -0,0 +1,4293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ModifyReturnsDifferentType - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

ModifyReturnsDifferentType

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The ModifyReturnsDifferentType check ensures that Modify methods return either void or a different type to the first input. Methods returning void will be returning the first input parameter, modified by the method, to the user in a visual programming environment. Further information is available here and here.

+

For example, the following method would fail because the return type is the same as the first input.

+

public static Panel AddOpenings(this Panel panel)

+

Whereas this method will pass because the return type is different from the input type.

+

public static Opening AddOpenings(this Panel panel)

+

And this method will pass because its return type is void and will return the first input object to the user in a visual programming environment.

+

public static void AddOpenings(this Panel panel)

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/ModifyReturnsSameType/index.html b/DevOps/Code Compliance and CI/Compliance Checks/ModifyReturnsSameType/index.html new file mode 100644 index 000000000..15c8ac21d --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/ModifyReturnsSameType/index.html @@ -0,0 +1,4291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ModifyReturnsSameType - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

ModifyReturnsSameType

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The ModifyReturnsSameType check ensures that Modify methods return the same type as the first input. This ensures that the modify methods are giving users back the same object type they're putting in.

+

For example, the following method would fail because the return type is not the same as the first input.

+

public static Opening AddOpenings(this Panel panel)

+

Whereas this method will pass because the return type matches the input type.

+

public static Panel AddOpenings(this Panel panel)

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/ObjectNameMatchesFileName/index.html b/DevOps/Code Compliance and CI/Compliance Checks/ObjectNameMatchesFileName/index.html new file mode 100644 index 000000000..9c33cdaec --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/ObjectNameMatchesFileName/index.html @@ -0,0 +1,4287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ObjectNameMatchesFileName - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

ObjectNameMatchesFileName

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The ObjectNameMatchesFileName check ensures that object names match the file names provided. This check is for object classes only within an oM. This ensures that objects and code files match 1:1 for people looking for object definitions within oM projects.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/PreviousInputNamesAttributeHasMatchingParameter/index.html b/DevOps/Code Compliance and CI/Compliance Checks/PreviousInputNamesAttributeHasMatchingParameter/index.html new file mode 100644 index 000000000..74e9970e3 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/PreviousInputNamesAttributeHasMatchingParameter/index.html @@ -0,0 +1,4302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + PreviousInputNamesAttributeHasMatchingParameter - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

PreviousInputNamesAttributeHasMatchingParameter

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The PreviousInputNamesAttributeHasMatchingParameter check ensures that a given PreviousInputNames attribute has a matching input parameter on a method.

+

This ensures that our documentation is accurate and valid for what users might see.

+

For example, the following method would fail this check because the input attribute does not match a given input parameter.

+
[PreviousInputNames("hello", "notHello")]
+public static void HelloWorld(double goodbye)
+{
+
+}
+
+

The correct implementation should instead look like this:

+
[PreviousInputNames("hello", "notHello")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/PreviousInputNamesAttributeIsUnique/index.html b/DevOps/Code Compliance and CI/Compliance Checks/PreviousInputNamesAttributeIsUnique/index.html new file mode 100644 index 000000000..9630136f1 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/PreviousInputNamesAttributeIsUnique/index.html @@ -0,0 +1,4303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + PreviousInputNamesAttributeIsUnique - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

PreviousInputNamesAttributeIsUnique

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The PreviousInputNamesAttributeIsUnique check ensures that there are not duplicate PreviousInputNames attributes for the same parameter.

+

This ensures that our documentation is accurate and valid for what users might see.

+

For example, the following method would fail this check because the input attribute is duplicated

+
[PreviousInputNamesAttributeIsUnique("hello", "notHello")]
+[PreviousInputNamesAttributeIsUnique("hello", "alsoNotHello")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+

The correct implementation should instead look like this:

+
[PreviousInputNamesAttributeIsUnique("hello", "notHello, alsoNotHello")]
+public static void HelloWorld(double hello)
+{
+
+}
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/Project-References-and-Build-Paths/index.html b/DevOps/Code Compliance and CI/Compliance Checks/Project-References-and-Build-Paths/index.html new file mode 100644 index 000000000..d5d98fe6e --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/Project-References-and-Build-Paths/index.html @@ -0,0 +1,4508 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Project References and Build Paths - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Project References and Build Paths

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

References

+

In order to aid people working on BHoM repositories across multiple platforms, and to avoid conflict between BHoM DLLs, project references to other BHoM repositories (for example, the Environment_oM from BHoM itself, or the Environment_Engine from BHoM_Engine) need to be set to a certain path.

+

This path should be to the ProgramData folder in the default drive of the machine. BHoM installs to the location :/ProgramData/BHoM folder, and all project files inside a toolkit have a postbuild event (see below) to copy their DLLs to the :/ProgramData/BHoM/Assemblies folder. By referencing DLLs in this location, it means people can install BHoM using an installer, clone a toolkit and begin developing without needing to clone and build the dependencies.

+

Therefore, DLL references should be set to:

+

$(ProgramData)/BHoM/Assemblies/TheDLL.dll

+

For example, if we want to reference Environment_oM from BHoM, our project reference should look like:

+

$(ProgramData)/BHoM/Assemblies/Environment_oM.dll

+

If the project reference is set to a copy of the Environment_oM DLL from another location, there is a risk that the DLL will be out of date to the main and you could therefore be building on top of an out of date framework.

+

If the project reference is not set to the example above, then this check will highlight that, and provide a suggestion of the path the DLL reference should have.

+

Exemptions

+

References to DLLs within your own solution file should be made as Project References, rather than as DLL references.

+

Copy Local

+

In order to prevent duplicate DLLs, some of which may be out of date, being placed in your repositories Build folder, and risk ending up in your Assesmblies folder run building BHoM_UI, the copy local property for all BHoM references should be set to false.

+

This check will also ensure this and flag any DLLs which do not have their copy local property set to false.

+

Exemptions

+

References can be set to copy local true if the project file is within the .ci/unit-tests folder path. DLLs referenced for NUnit unit tests require the DLLs to be copied locally and so this is valid.

+

Specific Version

+

In order to prevent DLLs being locked to specific versions, some of which may be out of date, the specific version property for all BHoM references should be set to false.

+

This check will also ensure this and flag any DLLs which do not have their specific version property set to false.

+

Build Folder

+

In order to facilitate the above, a projects output folder should be set to ..\Build\ to put all DLLs from your solution file in the correct folder. The Build folder is where the BHoM_UI looks to take DLLs for the install process when building locally.

+

This check will ensure that all build configurations (including Debug and Release) have their output folder path set to ..\Build\ and flag any instances where this is not correct.

+

Assembly Information

+

This section is only valid for projects utilising the new-style CSProject files, where an AssemblyInfo.cs file is not present. If an AssemblyInfo.cs file is present, then the compliance of this information can be found here.

+

Each DLL should have suitable assembly information to support automated processes and confirming the version of the code which the DLL was built against. This includes these three items:

+
    +
  • <AssemblyVersion>
  • +
  • <FileVersion>
  • +
  • <Description>
  • +
+

The AssemblyVersion should be set to the major version for the annual development cycle. This is set by DevOps, and will typically be a 4-digit number where the first number is the major version for the year, followed by three 0's - e.g. 5.0.0.0 for the 2022 development calendar (note, development calendars are based on release schedules as outlined by DevOps, not any other calendar system).

+

The FileVersion should be set to the current development milestone, which is the major version followed by the milestone, followed by two 0's - e.g. 5.3.0.0 for the development milestone running from June-September 2022.

+

The Description attribute should contain the full link to the repository where the DLL is stored, e.g. https://github.com/BHoM/Test_Toolkit for DLLs where the code resides in Test_Toolkit.

+

At the start of each milestone, BHoMBot will automatically uptick the AssemblyVersion and FileVersion as appropriate, and set the Description if it was not previously set. However, if you add a new project during a milestone, BHoMBot will flag these items as incompliant if they have not been resolved prior to running the project-compliance check. These items can be fixed by BHoMBot if you request BHoMBot to fix the project information.

+

PostBuild events

+

In order to facilitate a projects DLL being placed in the ProgramData folder for development testing, each project within a sln file must have its own postbuild event for copying its DLL to the correct location.

+

The postbuild event for this should be:

+

xcopy "$(TargetDir)$(TargetFileName)" "C:\ProgramData\BHoM\Assemblies" /Y

+

With nothing changed from the above example.

+

If your toolkit relies on external libraries to run, then the relevant project must also provide the suitable postbuild event to copy those DLLs to the ProgramData folder as well.

+

Similarly, if your toolkit has any datasets, then a suitable project within your toolkit must provide the suitable postbuild event to copy the datasets to the C:/ProgramData/BHoM/Datasets folder.

+

BHoMBot is not able to provide any automatic fixes for this compliance item, but will highlight if it detects that it is inaccurate

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/PropertyAccessorsHaveNoBody/index.html b/DevOps/Code Compliance and CI/Compliance Checks/PropertyAccessorsHaveNoBody/index.html new file mode 100644 index 000000000..af3cd8cf4 --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/PropertyAccessorsHaveNoBody/index.html @@ -0,0 +1,4294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + PropertyAccessorsHaveNoBody - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

PropertyAccessorsHaveNoBody

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

The PropertyAccessorsHaveNoBody check ensures that object property accessors do not have method bodies included with them.

+

For example, the following object definition will fail this check, because the get accessor has a body.

+

public double MyDouble { get { return 0.1; }; set; }

+

Whereas this property will fail because the set accessor has a body.

+

public double MyDouble { get; set { _val = value; }; }

+

This property will pass as a compliant property.

+

public double MyDouble { get; set; } = 0.0

+

This check is only operating on oM based objects. Objects within an Objects folder of an Engine (Engine/Objects) or Adapters are exempt from this check.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Compliance Checks/UIExposureHasDefaultValue/index.html b/DevOps/Code Compliance and CI/Compliance Checks/UIExposureHasDefaultValue/index.html new file mode 100644 index 000000000..52f0aa73e --- /dev/null +++ b/DevOps/Code Compliance and CI/Compliance Checks/UIExposureHasDefaultValue/index.html @@ -0,0 +1,4297 @@ + + + + + + + + + + + + + + + + + + + + + + + UIExposureHasDefaultValue - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

UIExposureHasDefaultValue

+ +

Summary

+

Severity - Fail

+

Check method - Here

+

Details

+

This check ensures that if you have set any Input attributes to have UIExposure.Hidden, they have default values for the parameters.

+

This is because inputs which are being hidden from the UI are unable to be given inputs by users, so suitable defaults must be provided if the input is to be hidden from a UI but accessible within code-use.

+

An example of the check failing is given below.

+
[Input("environmentObject", "Any object implementing the IEnvironmentObject interface that can have its tilt queried.")]
+[Input("distanceTolerance", "Distance tolerance for calculating discontinuity points, default is set to BH.oM.Geometry.Tolerance.Distance.", UIExposure.Hidden)]
+[Input("angleTolerance", "Angle tolerance for calculating discontinuity points, default is set to the value defined by BH.oM.Geometry.Tolerance.Angle.", UIExposure.Hidden)]
+public static double SomeMethod(this IEnvironmentObject environmentObject, double distanceTolerance, double angleTolerance = BH.oM.Geometry.Tolerance.Angle)
+{
+    return 0.0;
+}
+
+

In this example, the second Input for distanceTolerance does not have a default value set, while angleTolerance does.

+

To correct this, we need to give a default value to distanceTolerance, or remove the desire to have UIExposure.Hidden on the input.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/Continuous-integration/index.html b/DevOps/Code Compliance and CI/Continuous-integration/index.html new file mode 100644 index 000000000..486433423 --- /dev/null +++ b/DevOps/Code Compliance and CI/Continuous-integration/index.html @@ -0,0 +1,4508 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Continuous Integration (CI) - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Continuous Integration (CI)

+

Continuous Integration (CI) is the name given to the process of assisting our PR checks and resolving uncertainty in code status.

+

CI checks are built and maintained by the BHoM CI/CD team, but are operated automatically by our CI systems (including, but not limited to, AppVeyor, Azure DevOps and associated bots1).

+

The aim of CI checks is to increase confidence in our code, without unduly hindering our ability to prototype, develop, and extend the BHoM.

+

The pages within this section detail the CI checks we currently have operating, so that everyone can see how the checks are running and help ensure their PRs pass the checks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CheckProviderCommand
Check CoreBHoMBotTrigger by PR comment @BHoMBot check core
Check InstallerBHoMBotTriggered by PR comment @BHoMBot check installer
Check Project ComplianceBHoMBotTriggered by PR Comment @BHoMBot check project-compliance
Check Code ComplianceBHoMBotTriggered by PR Comment @BHoMBot check code-compliance
Check Documentation ComplianceBHoMBotTriggered by PR Comment @BHoMBot check documentation-compliance
Check Copyright ComplianceBHoMBotTriggered by PR Comment @BHoMBot check copyright-compliance
Check Dataset ComplianceBHoMBotTriggered by PR Comment @BHoMBot check dataset-compliance
Check Branch ComplianceBHoMBotTriggered by PR Comment @BHoMBot check branch-compliance
Check Unit TestsBHoMBotTriggered by PR Comment @BHoMBot check unit-tests
Check Null HandlingBHoMBotTriggered by PR Comment @BHoMBot check null-handling
Check SerialisationBHoMBotTriggered by PR Comment @BHoMBot check serialisation
Check VersioningBHoMBotTriggered by PR Comment @BHoMBot check versioning
Check Ready To MergeBHoMBotTriggered by PR Comment @BHoMBot check ready-to-merge
Check ComplianceBHoMBotTriggered by PR Comment @BHoMBot check compliance
Check RequiredBHoMBotTriggered by PR Comment @BHoMBot check required
+
+

Optional arguments

+

The following flags may be provided when requesting a check to request specific behaviour from the bot when running your requested check. One or more flags may be used at any one time - for example to trigger a full, forced, versioning check, you could use the command @BHoMBot check versioning -force -full. All flags are prepended by a dash (-). To see how an argument will affect a check, see the individual check page.

+ + + + + + + + + + + + + + + + + + + + +
FlagActionExample
-forceRequires a check to run even if it could be bypassed. For example, if a pull request does not change any CS or CSProj files, then the Versioning check may not run as it is time intensive. However, if you want to force the check to run, append -force to your request and it will run even if it could be bypassed.@BHoMBot check versioning -force
-quickRequests that the check run in a shortened format if available. For example, the Versioning check can opt to only compile the code in the pull request if no other repositories are depending on the work, allowing for a quicker versioning check compared to the default which will compile all the code used by the installers.@BHoMBot check versioning -quick
+
+

Use of CI checks

+

Not all checks are required on all repositories or on all branches, depending on the lifecycle state of the repository. The table below indicates which checks are required for a given repository state.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CheckPrototypeAlphaBeta (develop)Beta (main)
Coreimageimageimageimage
Installerimageimageimageimage
Project Complianceimageimageimageimage
Code Complianceimageimageimageimage
Documentation Complianceimageimageimageimage
Copyright Complianceimageimageimageimage
Dataset Complianceimageimageimageimage
Branch Complianceimageimageimageimage
Unit Testsimageimageimageimage
Null Handlingimageimageimageimage
Serialisationimageimageimageimage
Versioningimageimageimageimage
Ready to Mergeimageimageimageimage
+

1 See more notes on our approach to using and interacting with bots and automated processes as part of our Code of Conducts.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DevOps/Code Compliance and CI/index.html b/DevOps/Code Compliance and CI/index.html new file mode 100644 index 000000000..bf01d74df --- /dev/null +++ b/DevOps/Code Compliance and CI/index.html @@ -0,0 +1,4579 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Code compliance - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Code compliance

+

What is code compliance?

+

Code Compliance is the phrase used to determine how much the code written within the BHoM framework is in line with the rules/regulations/guidelines of BHoM development. The compliance rules have evolved following the initial ethos of BHoM and been carefully refined as BHoM has developed.

+

The core of the rules however remains the same - that the code should be architected in such a way to facilitate, and promote, adoption and collaboration by any engineer using the BHoM. The components they see on the UI, should reflect what they can see in the code, the code should be easy to navigate by those wishing to find information, and the style from toolkit to toolkit should be consistent. All of this allows new members of BHoM to quickly get to grips with the basics, and the ability for multiple people to work on multiple toolkits is enhanced as a result.

+

The rules, regulations, and guidelines set out in this section of the wiki are there to give us reference for writing sustainable, maintainable, and compliant code within the framework of BHoM. They are our standards by which we should all follow.

+

The compliance laid out in the following pages does undergo periodic review by the DevOps team, as styles develop, and the guidance evolves, so if you feel something isn't quite right or is unclear, please feel free to open a discussion.

+

Types of compliance

+

Compliance can be broken into the following categories.

+
    +
  • Code Compliance - this is the compliance of code which is written within the BHoM framework
  • +
  • Documentation Compliance - this is the compliance of the documentation that aids users and wraps around code
  • +
  • Project Compliance - this is the compliance of the repository, its associated project files, and planning operations
  • +
+

Compliance results

+

Compliance results can form one of three outcomes.

+
    +
  • Pass - everything is good, compliant, and meets the guidance available
  • +
  • Warning - a piece of code is not compliant, but it is deemed not to be so severe as to prevent a PR being merged, but it should be addressed as quick as possible
  • +
  • Fail - a piece of code is not compliant, and it is critical to resolve it before the PR is merged
  • +
+

Toolkit and Discipline Leads are responsible for deciding whether warning results are acceptable on their toolkit on a case-by-case basis.

+

Current compliance checks

+

Correct at time of writing.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CheckSeverityCompliance Type
Assembly InformationFailProject
AttributeHasEndingPunctuationWarningDocumentation
EngineClassMatchesFilePathFailCode
HasConstructorFailCode
HasDescriptionAttributeFailDocumentation
HasOneConstructorFailCode
HasOutputAttributeWarningDocumentation
HasPublicGetFailCode
HasSingleClassFailCode
HasSingleNamespaceFailCode
HasUniqueOutputAttributeFailDocumentation
HasUniqueMultiOutputAttributesFailDocumentation
HasValidConstructorFailCode
HasValidCopyrightFailCopyright
HasValidOutputAttributeFailDocumentation
HasValidMultiOutputAttributesFailDocumentation
HasValidPreviousVersionAttributeFailDocumentation
HiddenInputsAreLastWarningDocumentation
InputAttributeHasMatchingParameterFailDocumentation
InputAttributeIsUniqueFailDocumentation
InputAttributesAreInOrderFailDocumentation
InputParameterStartsLowerFailCode
IsDocumentationURLValidFailDocumentation
IsExtensionMethodFailCode
IsInputAttributePresentWarningDocumentation
IsPublicClassFailCode
IsPublicPropertyFailCode
IsStaticClassFailCode
IsUsingCustomDataWarningCode
IsValidCreateMethodFailCode
IsValidConvertMethodNameFailCode
IsValidCreateMethodNameFailCode
IsValidDatasetFailDataset
IsValidEngineClassNameFailCode
IsValidIImmutableObjectFailCode
IsVirtualPropertyFailCode
MethodNameContainsFileNameFailCode
MethodNameStartsUpperFailCode
ModifyReturnsDifferentTypeFailCode
ObjectNameMatchesFileNameFailCode
PreviousInputNamesAttributeHasMatchingParameterFailDocumentation
PreviousInputNamesAttributeIsUniqueFailDocumentation
Project References and Build PathsFailProject
PropertyAccessorsHaveNoBodyFailCode
UIExposureHasDefaultValueFailDocumentation
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Best practices/Branching-Strategy/index.html b/Development/Best practices/Branching-Strategy/index.html new file mode 100644 index 000000000..bf1947cd6 --- /dev/null +++ b/Development/Best practices/Branching-Strategy/index.html @@ -0,0 +1,4698 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Branching Strategy - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Branching Strategy

+

The primary branch which forms our codebases single source of truth is the main branch across all repositories. Depending on the category of the repository, there may be protections in place for the development of code and merging to main branches. As a repository progresses through its lifecycle from prototype to beta, the level of protections change as appropriate.

+

Creating branches

+

No code should be committed directly to the main branch of any repository, all code should be produced on an independent branch and deployed to main via a Pull Request.

+

If you are using GitHub desktop, you should make sure you are on the correct default (main or develop depending on the repository state - see below) branch and refresh it to ensure you have the latest version on your machine.

+

Then create a new branch by clicking on the Current branch button and select New branch.

+

Make sure to check this page for the guidelines on when to create a branch and when not to.

+

img

+

You should see that your repository history has now switched to a new branch.

+

img

+

From there you are ready to work on your code. Any commit that you will do, will be on that new branch.

+

Branch naming convention

+

For all branches where code development is to take place, the following naming convention should be adopted.

+

RepositoryOrProjectName-#X-Description

+

where X is the issue number you are solving.

+

Both the Repository or Project name and the Issue number should refer to the base issue being solved.

+

For example, if you are working in IES Toolkit, aiming to resolve issue 99 (which fixes window placement), the branch name should be IES_Toolkit-#99-FixingWindows.

+

If you're working on a repository with multiple disciplines, such as BHoM_Engine, then you can name the branch after the specific engine you are working on. For example, if you are working in the Environment Engine, aiming to resolve issue 103 (which fixes window creation), the branch name should be Environment_Engine-#103-FixWindowCreation.

+

This branch naming convention is particularly important when producing development installers - BHoMBot will use the name of the branch to calculate where to place installer artefacts which are generated to aid in testing the Pull Request. If the branch is not named in this convention, BHoMBot will be unable to calculate this and you will lose out on CI benefits.

+

Branches in dependant repositories - MUST be named identically

+

For instance if a change in the BHoM will lead to a change needed in some sub-repositories, all of those sub-repositories **MUST get the same branch name**. This is essential for our (CI) process to correctly check changes spanning across multiple repository Pull Requests.

+

For example, if you are adding an object in BHoM, and adding a Query method for that method in BHoM_Engine, both repositories should share the same branch name, such as Environment_oM-#103-AddLightObject - this is to ensure when we run CI checks such as Installer and Versioning, the check can find both Pull Requests and run them together within the bot ecosystem.

+

Prototypes

+

Prototype repositories use only a main branch for their code development. The main branch should be protected to the level that it requires a Pull Request to merge code, however, there is no requirement on Prototype Repositories for a Pull Request to receive a review. The Pull Request can be raised and merged instantly (depending on any required CI checks) without intervention from a reviewer. Reviews are still an option for Prototype repositories should people wish to discuss changes before a merge, but they are not a requirement.

+

There is no automatic deployments of Prototype repositories - the only way for code to be utilised is for it to be built from source or the DLLs shared between users.

+

When creating a new branch for the addition of code to a Prototype repository, branch from an up to date version of the main branch.

+

Alpha State

+

Repositories deployed in an Alpha state use only a main branch for their code development. The main branch should be protected to the level that it requires a Pull Request with at least 1 approving review prior to the code being merged.

+

Once code is merged to the main branch, the code will be deployed via alpha installers and available for more general consumption via Installers. Therefore code which is deployed to main must meet certain CI criteria before being able to merge the Pull Request.

+

When creating a new branch for the addition of code to an Alpha repository, branch from an up to date version of the main branch.

+

Beta State

+

Repositories deployed in a Beta state use both a main branch and a develop branch for their code development. The develop branch is set as the default branch.

+

The main branch continues to serve as the repository's single source of truth and is the branch which is deployed via beta installers at the end of each milestone.

+

The develop branch serves as a staging ground for development of features and larger pieces of work which is deployed via alpha installers.

+

The difference here for Beta repositories is that the main branch should only be updated each milestone with code from the develop branch which has been suitable tested and reviewed and deemed to be fit for purpose for general deployment in the Beta installers available on BHoM.xyz and other platforms. Utilising a different branch for general development (develop) from the Beta deployed branch (main) grants us a degree of control over what is deployed at the end of each milestone and beta.

+

For repositories which are undergoing large portions of work, perhaps large refactors or additional features, targeting new APIs, etc., it may not be suitable to deploy that work to a Beta where the work spans across multiple milestones of development. If this work was deployed to main for Alpha testing, it would then be automatically deployed to Beta at the end of the milestone when it may not be ready. Deploying to develop for Alpha testing then allows us to choose not to deploy that to main at the end of the milestone, allowing the Beta to contain only the deployable code that is up to the adequate standards without hindering development, or requiring Pull Requests to stay open for a lengthy time and take more resource to resolve when the time is right.

+

Additionally, separating the main Beta branch from the develop Alpha branch allows us to patch the Beta for critical bugs during a milestone of development, enabling the release of curated, up to standard code that resolves a specific bug without also deploying code which may be under ongoing development.

+

All Pull Requests for Beta repositories should aim to merge into the develop branch unless authorised by DevOps to merge into the main branch to perform a Beta Patch.

+

When creating a new branch for the addition of code to a Beta repository, you should branch from the branch where the code aims to end up. For example, if you are developing a new feature which will merge into the develop branch, then you must branch from an up to date version of the develop branch. However, if you are providing a bug fix for a Beta Patch, which aims to merge directly into the main branch, then you must branch from an up to date version of the main branch.

+

Branch Protections

+

This table gives an overview of the protections required for each individual type of repository.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Protection SettingPrototypeAlphaBeta (develop)Beta (main)
Require a Pull Request before Mergingimageimageimageimage
Require Approvalsimageimageimageimage
Minimum Number of ApprovalsN/A111
Dismiss stale pull request approvals when new commits are pushedimageimageimageimage
Require review from Code Ownersimageimageimageimage
Restrict who can dismiss pull request reviewsimageimageimageimage
Allow specified actors to bypass required pull requestsimageimageimageimage
Require approval of the most recent pushimageimageimageimage
Require status checks to pass before mergingimageimageimageimage
Require branches to be up to date before mergingimageimageimageimage
Status Checks that are requiredSee hereSee hereSee hereSee here
Require conversation resolution before mergingimageimageimageimage
Require signed commitsimageimageimageimage
Require linear historyimageimageimageimage
Require deployments to succeed before mergingimageimageimageimage
Lock branchimageimageimageimage
Do not allow bypassing the above settingsimageimageimageimage
Restrict who can push to matching branchesimageimageimageimage
Restrict pushes that create matching branchesimageimageimageimage
People, teams, or apps with push accessN/AMerge TeamMerge TeamDevOps Team
Allow force pushesimageimageimageimage
Allow deletionsimageimageimageimage
+

Branching diagrams

+

main branch only

+

image

+

main branch with a develop branch

+

image

+

Stale branches

+

A stale branch is defined as a branch of code which has not had any commit activity in 6 months or longer from the date of the last commit or any discussion via a pull request (regardless of state) in those 6 months. Branches that are deemed to have gone stale may be subject to deletion during repository clean ups that occur during a milestone alongside pull request closures and other spring-cleaning tasks which help keep the code base clean from too much noise.

+

If a branch is required for ongoing work, but does not have a pull request associated to it and has not had commit actiivty within 6 months, may be eligible to remain available if good reason can be provided for doing so. Good reason can be provided via an issue, or reaching out to the DevOps team directly.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Best practices/Coding-together-avoiding-conflicts/index.html b/Development/Best practices/Coding-together-avoiding-conflicts/index.html new file mode 100644 index 000000000..6b586256d --- /dev/null +++ b/Development/Best practices/Coding-together-avoiding-conflicts/index.html @@ -0,0 +1,4387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Coding together avoiding conflicts - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Coding together avoiding conflicts

+

Since multiple people may be working on the codebase at the same time please remain aware of other branches on the same repository and keep an eye out for potential conflicts between them, this is especially true of open Pull Requests. If there are changes on parallel branches, and especially ones you know will cause conflicts, there is no substitute to reaching out to the author(s) of those changes and discussing the intent and goals behind yours and theirs and aligning the best way to resolve them. You may find that one of you may be making a change that will actually make the other's goals easier to achieve or even unnecessary and save some work. Someone pausing development may be the best resolution in some cases, in others continuing and dealing with the conflicts later may be, and in others there could be refactoring work that could be done now to save this effort being necessary.

+

Be sure to regularly fetch and check that your branch integrates cleanly with master, if it does not please rectify these conflicts on your branch.

+

Core Contributors are expected to resolve conflicts on their PRs in order to have their PR accepted and merged. Maintainers should expect to assist external contributors with this process or otherwise handle them at merge time. Also see GitHub's about merge conflicts page

+

Never Work on the Same Files

+

The challenge is therefore to make sure that we never have two people modifying the same files in two separate branches. While it is easy to be aware which code file you are modifying, it is very important to understand that there are a few files maintained by Visual Studio that can also be the source of clashes:

+
    +
  • Solution file: This file is modified every time a project is added for example. This means we can never have two people creating a new project in two different branches. If you know you will have to do that, you have to block the entire repository for the duration of your sprint. To block a repo, make sure your issue and card on the SCRUM board follow the naming convention of a repo-level issue.
  • +
  • Project file: This file is modified every time you add a file to the project. This happens a lot since you create a file every time you create a new section of code. It will also be modified if you move a file. Because of this, two people are never allowed to work on the same project at the same time. To block the project, make sure your issue and card on the SCRUM board follow the naming convention of a project-level issue.
  • +
+

FAQ

+

I have to make changes across multiple projects at the same time, what do I do?

+

If it is only two projects, you can simply name your issue and branch with the two project names instead of just one. If this is more than that, you will have to block the entire repository. In that situation, it is frequent that unplanned changes will have to be made in other projects anyway so it is safer to block the whole repository.

+

I am not sure if my code will be limited to a single project. There might be ripple effects. What do I do?

+

In doubt, it is safer to block the whole repository. It is very annoying for everyone else though so only do it if it is clear the side effect of your changes cannot be dealt with in a separate issue/PR. Also make sure you keep your sprint as short as possible so you limit the time you are blocking everyone. One thing to consider is to work only locally until you know for sure the effect your code has so you can create the branch accordingly.

+

My issue is super urgent but someone else is already blocking the project/repository.

+

You can always work locally. Just don't create a branch yet and solve the problem on your machine. Contact the other person blocking you to coordinate. As soon as his/her PR is merged, you can pull the latest changes on your machine and create your pull request.

+

I am creating a branch that will never be merged. Is there a solution for that?

+

Yes, you can use this naming convention instead: NeverMerge-IssueX-Description. As you can see, we have replaced the project or repository name with NeverMerge. This is a very rare case though since 99.9% of the code should be meant to be merged.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Best practices/Merge-Teams/index.html b/Development/Best practices/Merge-Teams/index.html new file mode 100644 index 000000000..42aa8944e --- /dev/null +++ b/Development/Best practices/Merge-Teams/index.html @@ -0,0 +1,4334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Merge Teams - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Merge Teams

+

Merge teams are set up to deploy code to protected branches (main or develop in most cases) following a successful Pull Request review process.

+

Merge teams are managed by DevOps, and inclusion or exclusion from a team may occur at any time.

+

Merge teams will be reviewed at regular intervals to ensure they are up to date and reflective of the current development needs.

+

Creating a merge team

+

Creation of a merge team should be done when a repository is created, regardless of whether that repository requires Pull Request reviews or not. The merge team should be named the same as the repository they will be collaborators for. Discipline level teams may be created if approved by DevOps to handle multiple repositories, but this should be in addition to a specific merge team for that repository.

+

Adding people to a merge team

+

A request should be made to DevOps to add an individual to a merge team. Merging Pull Requests is a responsible action which results in code being potentially deployed via Alpha or Beta installers. As such, people who are merging Pull Requests need to be competent in discharging this duty. DevOps is responsible for determining whether an individual is competent in this role and can be added to a merge team. The decision of DevOps is final, however, individuals may make future requests to be added to merge teams and previous prohibition will not be a detrimental factor in a subsequent decision. DevOps will ask individuals to prove competency in a manner appropriate at the time of the request, but will include a review of procedures and policies to ensure the individual understands the broader development picture, as well as the associated risks of merging code.

+

Removing people from a merge team

+

Any individual can request to be removed from a merge team and DevOps will action this as soon as is appropriate without question.

+

Discipline Code Leads may request individuals to be removed from a merge team they are responsible for.

+

DevOps may remove any individual from a merge team at any time if appropriate.

+

The DevOps Merge Team

+

The DevOps merge team is a separate team to repository teams, and exists for the purpose of protecting merging to the main branch of repositories included in the Beta. Individuals will only be added to this merge team if they are part of the DevOps team.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Best practices/Toolkits-targeting-multiple-versions-of-the-same-software/index.html b/Development/Best practices/Toolkits-targeting-multiple-versions-of-the-same-software/index.html new file mode 100644 index 000000000..f97949bfc --- /dev/null +++ b/Development/Best practices/Toolkits-targeting-multiple-versions-of-the-same-software/index.html @@ -0,0 +1,4453 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Targeting multiple API versions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Targeting multiple API versions

+

It might happen that a Toolkit targeting a specific software will have to reference different assemblies for different versions of the software.

+

For example, this happens for ETABS_Toolkit. We will take it as an example in this page.

+

In ETABS, the various versions of the software have different API assemblies, and the assemblies have different names depending on the software version. For example: ETABS version 2016 has an API assembly named ETABS2016.dll; ETABS version 2017 has one named ETABSv17.dll.

+

For this reason, it's important to set the Build Configuration of the solution in a manner that allows the needed flexibility and maintains scalability.

+

For the sake of semplicity we will refer to this as "versioning" in this wiki page.

+

Guidelines

+

Limit the versioning to the VS Projects that need it

+

For example, ETABS_Toolkit needs to reference the software API (and therefore different versions of it) only in the project ETABS_Adapter.

+

This means that the other projects of the toolkit, namely ETABS_Engine and ETABS_oM, can avoid the problem altogether. No action should be taken on them.

+

If only one VS Project needs versioning, make sure the others projects' Build configuration target the base build.

+

You can set this in Visual Studio Build menu → Configuration Manager.

+

This means that Projects that do not need versioning – in the ETABS example the Engine and the oM – have to: +- For "Debug-type" builds: target the base Debug configuration; +- For "Release-type" builds: terget the base Release configuration.

+

The following screenshot shows an example for "Debug-type" build: +image

+

Make sure builds are having clear separate assembly name

+

The assembly name can be set by modifying the Project's .csproj file.

+
+

More info on how to modify the .csproj

+

+This can be done by:

+
    +
  • +

    In VS, right click the project in Solution Explorer → Unload Project → right click again → edit .csproj. Edit, save, then right-click again on the project and do Reload Project.

    +
  • +
  • +

    OR by navigating to the project folder and editing the .csproj directly. +

    +
  • +
+
+

The AssemblyName has to be defined so that it reflects the build version (e.g. 2017, 2018, etc.) and to be consistent with the naming conventions adopted for the specific Toolkit.

+

See the following example for the ETABS as an example:

+
  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug17</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    ...
+    <AssemblyName>ETABS17_Adapter</AssemblyName>
+    ...
+  </PropertyGroup>
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug18</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    ...
+    <AssemblyName>ETABS18_Adapter</AssemblyName>
+    ...
+  </PropertyGroup>
+
+

Test all builds are coming out correctly

+

Once you are done, please try to build using all configurations.

+

To ensure you are doing this correctly, go to the Toolkit's Build folder and delete all its contents every time you test a different Build.

+

Make sure the a Build config is added to the BHoM installer

+

Contact the Toolkit's responsible - they will do it for you or assist you in doing that.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Concepts/Code-Attributes/index.html b/Development/Concepts/Code-Attributes/index.html new file mode 100644 index 000000000..2780bde47 --- /dev/null +++ b/Development/Concepts/Code-Attributes/index.html @@ -0,0 +1,4513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM Attributes - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM Attributes

+

The BHoM framework makes use of C# Attributes to annotate and explain classes, methods and properties. Attributes used is a combination custom attributes created in the BHoM and the one provided by the core C# libraries.

+

The information provided in the attributes will be used by the UI and help control what is exposed as well as give the end user a better understanding of what your method is supposed to do.

+

To make use of the custom attributes you will need to make sure that your project has a reference to the Base_oM. You will also need to to make sure that the following usings exists in the .cs file you want to use the attributes in:

+
using BH.oM.Base.Attributes;
+using System.ComponentModel;
+
+

The attributes are described below.

+

Description

+

Only consists of a single string and can be used on a class, method or a property. Used to give a general explanation of what the class/ method or property is doing. You can only add one description to each entity. Example:

+
        [Description("Calculates the counterclockwise angle between two vectors in a plane")]
+        public static double Angle(this Vector v1, Vector v2, Plane p)
+        {
+            //....code
+        }
+
+

Description authoring guidelines ✏️

+

We should be aiming for all properties, objects and methods to have a description. With only the very simplest of self explanatory properties to not require a description by exception - and indeed only where the below guidelines can not be reasonably satisfied.

+

So what makes a good description?

+
    +
  1. A description must impart additional useful information beyond the property name, object and namespace.
  2. +
  3. Further to a definition, the description is an opportunity to include usage guidance, tips or additional context.
  4. +
  5. The description is a place you can include synonyms etc. to help clarify for others in different regions/domains, being inclusive as possible.
  6. +
  7. Also don't forget the addition of a Quantity Attribute can be used now, appropriate for Doubles and Vectors.
  8. +
+

DisplayText

+

Only consists of a single string and can be used on enums. Used to provide a human-friendly text version of the enum in the UI. Example:

+
    public enum Market
+    {
+        Undefined,
+        [DisplayText("Europe ex UK & Ireland")]
+        Europe_ex_UKAndIreland,
+        India,
+        [DisplayText("Middle East")]
+        MiddleEast,
+        [DisplayText("Other UK & Ireland")]
+        Other_UKAndIreland,
+        ...
+    }
+
+

Input

+

Used on methods to describe the input parameters. Consists of two strings, name and description. The name need to correspond to the name of the parameter used in the method and the description is used the explain the methods purpose. Multiple input tags can be used for the same method. Examples:

+
        [Input("obj", "Object to be converted")]
+        public static string ToJson(this object obj)
+        {
+            //....code
+        }
+
+
        [Input("externalBoundary", "The outer boundary curve of the surface. Needs to be closed and planar")]
+        [Input("internalBoundaries", "Optional internal boundary curves descibing any openings inside the external. All internal edges need to be closed and co-planar with the external edge")]
+        public static PlanarSurface PlanarSurface(ICurve externalBoundary, List<ICurve> internalBoundaries = null)
+        {
+            //....code
+        }
+
+

Output

+

Used on methods to describe the resulting return object. Consists of two strings, name and description. The name will be used by the UIs to name the result of the method and the description will help explain the returned object. You can only add one output to each method. Example:

+
        [Output("List", "Filtered list containing only objects assignable from the provided type")]
+        public static List<object> FilterByType(this IEnumerable<object> list, Type type)
+        {
+            //....code
+        }
+
+

NotImplemented

+

Used on methods that are not yet implemented. Method with this tag will not be exposed in the UIs. Example:

+
        [NotImplemented]
+        public static double Length(this NurbsCurve curve)
+        {
+            throw new NotImplementedException();
+        }
+
+

PreviousVersion

+

The previous version attribute helps with code versioning of methods when a method has been changed in terms of name, namespace or input parameters. Example of how to use it see Method versioning

+

Replaced

+

Used on a method that is being replaced by another method and is to be deleted in coming versions while no automatic versioning is possible. This attribute should only be used when Versioning is impossible! This attribute will hide the method from the method tree in the UIs as long as the FromVersion property is lower or equal to the assembly file version and thereby make it impossible to create any new instances of the method. Any existing scripts will still work and reference the method. To read more about method deprecation strategy please see here.

+

The deprecated attribute has four properties:

+
    +
  • string Description - Description as to why the method is being replaced.
  • +
  • Version FromVersion - Which version was this method replaced. Here you generally only have to specify the first two digits, for example 2.3.
  • +
  • Type ReplaceingType - Where can you find any replacing method (if it exists)
  • +
  • string ReplacingMethod - What is the name of the replacing method (if it exists)
  • +
+

Example:

+
        [Replaced(new Version(2,3), "Replaced with CurveIntersections.", null, "CurveIntersections")]
+        public static List<Point> CurvePlanarIntersections(this Arc curve1, Circle curve2, double tolerance = Tolerance.Distance)
+        {
+            //....code
+        }
+
+

ToBeRemoved

+

Attribute only to tag a class or method that is to be removed. This attribute should only be used when Versioning is impossible! This attribute will hide the method from the method tree in the UIs as long as the FromVersion property is lower or equal to the assembly file version and thereby make it impossible to create any new instances of the method.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Concepts/Coding-Style/index.html b/Development/Concepts/Coding-Style/index.html new file mode 100644 index 000000000..9b1347a52 --- /dev/null +++ b/Development/Concepts/Coding-Style/index.html @@ -0,0 +1,4498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM's coding style and conventions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM's coding style and conventions

+

General C# conventions

+

Our coding style generally follows the Microsoft guidelines on C#.

+

However, to attain a higher level of clarity and transparency, BHoM code also adheres to additional customised rules and style guidelines.

+

Additional conventions

+

BHoM code also adheres to customised rules and style guidelines. These are in place for several reasons, mainly:

+
    +
  • to make easier to read and contribute to the codebase;
  • +
  • to ensure the functionality can be correctly exposed to the UIs;
  • +
  • to organise functionality and classes in a tidy, easy-to-find manner.
  • +
+

Access modifiers

+

Access modifiers specify the accessibility level of type and type members. They denote whether a type or member can be used by other code in the same assembly, and in other assemblies.

+
    +
  • In line with BHoM's focus on clarity and transparency, we generally use the public access modifier, which allows a type or member to be accessed by any other code in the same assembly or other assembly that reference it.
  • +
  • When absolutely necessary, we use the private access modifier to limit the access of a type or member to only code in the same class.
  • +
  • Although C# provides many access modifiers, we limit our use to the two mentioned above.
  • +
+

Filenames, objects and methods

+
    +
  • A .cs file can contain only 1 (one) class, and there is no concept as a Helper or Utils class.
  • +
  • For oM objects the name of the .cs file is the Name (excluding the namespace) of the Object (class), e.g. the Line class is in the Line.cs file.
  • +
  • For engine methods, a file can only contain methods whose name start or end with the name of their file file, e.g. Flip(Line line) and Flip(Arc arc) are in the same file Flip.cs, and FilterPanels and FilterOpenings can both reside inside a Filter.cs file.
  • +
+

Folders and namespaces

+

Namespaces and the folder structure that contains the .cs files have a close relationship. To define the correct folder structure helps keeping the relationship with the namespaces. This, in turn enables additional functionalities, such as deriving the web address of the source code of a method.

+

For a Class, an Attribute, an Enum, and an Interface, the folder structure respects the following rules:

+
    +
  • +

    If a file is in a sub folder, the namespace of the entity must follow: if Bar is in a sub folder Elements, its namespace must suffix the Elements word BH.oM.Structure.Elements.

    +
  • +
  • +

    An Enum must be in a separate folder Enums. Although, the namespace remains unchanged, and does not follow - i.e. Enums is appended as suffix. For example BarFEAType is in the sub folder Elements, and it is an enum. Its namespace respects A., so it contains the Elements word, but does not contain the Enum word: BH.oM.Structure.Elements. At the same time, since it is an Enum it is in an Enums folder.

    +
  • +
  • +

    The same rule as B. applies to:

    +
  • +
  • Attribute => Attributes
  • +
  • Interface => Interfaces
  • +
+

Enum ordering

+

The order an Enum is written is the order in which it is displayed in the UI dropdown options. This order is therefore important to the UX of using the Enum within a workflow. The order should therefore follow one of the following conventions. There may be occasions when an Enum order does not follow the conventions below. These occasions should be clearly documented with the reasons why a different convention has been followed.

+

Alphabetical

+

The order of the Enum should be alphabetical (following British-English spelling conventions) in ascending order (i.e. A-z).

+

Caveat for Undefined

+

If your Enum option has an Undefined option to denote a default unset option, then this should go as the first option at the top of the Enum.

+

For an example of an Enum following this convention, see the Environment Panel Type Enum.

+

Logical

+

The order of the Enum can be in a logical order instead where this makes more sense than alphabetical. An example of such an Enum might be one that records the size of an object. In this case, the options might be:

+
ExtraSmall
+Small
+Normal
+Large
+ExtraLarge
+
+

This order for the Enum makes logical sense and provides a good UX where users will have context from the name of the Enum that the order might be different to alphabetical (e.g. the name might be UnitSize).

+

Yoda condition

+

For conditional statements, the variable expression should be placed in front of the constant expression. When this order is reversed, it is referred to as a "Yoda condition". For readability, we avoid using Yoda conditions in our code base. An example of both is given below.

+
string str = "hello world" 
+
+if (str == "BHoM") { /* … */} //most common convention - preferred for BHoM development
+
+else if ("BHoM" == str) {/* … */} //Yoda style, as the constant "BHoM" precedes the string variable
+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Concepts/Handling-Exceptional-Events/index.html b/Development/Concepts/Handling-Exceptional-Events/index.html new file mode 100644 index 000000000..a3a2624b6 --- /dev/null +++ b/Development/Concepts/Handling-Exceptional-Events/index.html @@ -0,0 +1,4479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Logging and exceptions - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Logging and exceptions

+

BHoM Logging

+

You can record events in the Log by using +- BH.Engine.Base.Compute.RecordError(string message) +- BH.Engine.Base.Compute.RecordWarning(string message) +- BH.Engine.Base.Compute.RecordNote(string message)

+

You can access all event logged since the UI was started by calling BH.Engine.Base.Query.AllEvents().

+

Dealing with errors

+

Things don't always run according to plan. Two typical situations can occur: +- The input value your method received are invalid or insufficient to generate the output. +- The methods you call inside your method are failing

+

In either case, you are generally left with a few choices: +- throw an exception, +- return a null value, +- return a dummy value.

+

The first option stops the execution of the code completely while the other two allows things to continue but with the risk of the problem remaining unnoticed. A lot of times, none of those options are satisfactory. Let's take a simple example:

+
public List<object> MyMethod(List<BHoMObject> elements)
+{
+   List<object> results = new List<object>();
+   foreach (BHoMObject element in elements)
+      results.Add(DoSomething(element));
+   return results;
+} 
+
+

If DoSomething() throws an exception, this method will fail and pass on the exception. This might be the desired behaviour but we might also want to return all the successful results and just ignore the failing ones. In that case, we could write:

+
public List<object> MyMethod(List<BHoMObject> elements)
+{
+   List<object> results = new List<object>();
+   foreach (BHoMObject element in elements)
+   {
+      try 
+      {
+         results.Add(DoSomething(element));
+      }
+      catch {}
+   }
+   return results;
+} 
+
+

This does the job. But it also hide completely the fact that an error occurred for some of the elements so the results are incomplete.

+

This is why we have added a log system to the BHoM so all exceptional events can be recorded and passed to the UI.

+

Recording Events

+

If we use the log, the code above would look like this:

+
using BH.Engine.Base;
+
+public List<object> MyMethod(List<BHoMObject> elements)
+{
+   List<object> results = new List<object>();
+   foreach (BHoMObject element in elements)
+   {
+      try 
+      {
+         results.Add(DoSomething(element));
+      }
+      catch 
+      {
+         Compute.RecordWarning("Element " + element.BHoM_Guid + " failed");
+      }
+   }
+   return results;
+} 
+
+

There are 3 levels of event you can record: +- Error: RecordError() +- Warning: RecordWarning() +- Note: RecordNote()

+

In Grasshopper, they will look like this:

+

img

+

So the UI components will automatically expose all the events that occurred during their execution.

+

So when should I use each type of event?

+

Besides fatal errors, RecordError() should be used in cases when we are not able to return any result for the provided input: +

public static Point Centroid(this PolyCurve curve, double tolerance)
+{
+  if (!curve.IsClosed(tolerance))
+  {
+    Base.Compute.RecordError("Input curve is not closed. Cannot calculate centroid.");
+    return null;
+  }
+  [...]
+}
+
+Note that errors most often go with returning null (or .NaN in case of doubles).

+

RecordWarning() is for all kind of situations when the result is possible to compute, but we cannot ensure if it is 100% correct. It is also suitable if provided object has been modified in not certainly desired way: +

public static Vector Normal(this PolyCurve curve, double tolerance)
+{
+  if (curve.IsSelfIntersecting(tolerance))
+    Base.Compute.RecordWarning("Input curve is self-intersecting. Resulting normal vector might be flipped.");
+
+  [...]
+}
+

+

At last RecordNote() is meant for the cases when everything run correctly but there is still some info that we would like to communicate to the end user: +

public override List<object> Push([...])
+{
+  [...]
+  if (pushConfig == null)
+  {
+    BH.Engine.Base.Compute.RecordNote("Revit Push Config has not been specified. Default Revit Push Config is used.");
+    pushConfig = new RevitPushConfig();
+  }
+  [...]
+}
+

+

As one can see, there is no very strict convention on when to use each level of event. However, these examples should illustrate their intended purpose.

+

Accessing All Events Since the Start

+

If you want to get the list of all the events that occurred since you started your script/program, you can use BH.Engine.Reflection.Query.AllEvents(). In Grasshopper, it will look something like this:

+

img

+

As you can see, events are also BHoM object that you can explode as any other typical BHoM object.

+

What About Exceptions?

+

Does that mean that we should stop using exception? No!

+

If your method ends up in a situation where it could not return any meaningful output, it should still throw an exception. Any method that catches an exception, on the other hand, should ALWAYS record something in the Log to make the user aware of what happened.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Concepts/Null-Handling/index.html b/Development/Concepts/Null-Handling/index.html new file mode 100644 index 000000000..ea3743e7e --- /dev/null +++ b/Development/Concepts/Null-Handling/index.html @@ -0,0 +1,4356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Null handling - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Null handling

+

Null Handling is the practice of protecting against null inputs to methods within the engines and adapters.

+

Null inputs can throw errors that are unhelpful to the user, typically a object is not set to an instance of an object exception, which does not provide the user with much information on how to resolve this problem within their chosen UI.

+

As such, it is good practice to ensure all of the inputs to a method are valid before trying to run operations on them. Take the following method as an example.

+
public static string GetName(BH.oM.Environment.Elements.Panel panel)
+{
+    string name = "";
+    name += panel.Name + " ";
+    name += panel.Construction.Name;
+    return name;
+}
+
+

If panel is null, then the line name += panel.Name + " "; will throw a NullReferenceException as you cannot get the Name property of an object with no data associated to it (null). This may then confuse the user. Therefore, we should check whether the panel is null and tell the user before using it.

+
public static string GetName(BH.oM.Environment.Elements.Panel panel)
+{
+    if(panel == null)
+    {
+        BH.Engine.Reflection.Compute.RecordError("Panel cannot be null when querying the name. The panel should have data associated to it and be a valid instantiation of the object."); //Suitable error message that helps the user understand what's going on
+        return ""; //A suitable return - you could `return null;` here instead if needed
+    }
+
+    string name = "";
+    name += panel.Name + " ";
+    name += panel.Construction.Name;
+    return name;
+}
+
+

The return from a null check should be appropriate for the return object type. For complex objects (e.g. a BHoM object return type, such as a Panel or Bar), returning null should be appropriate, as empty objects (such as return new Panel();) will likely cause more problems down the line if the object is not null, but has no data. For primitive types (e.g. string, int) then returning a suitable default is appropriate, such as an empty string (""). For numbers (int, double, etc.), returning a number should be carefully considered. 0 may be a valid response to the method that the downstream workflow will rely on, so consider returning negative numbers (e.g. -1) instead, or numbers outside the realm of reality for the equation (such as 1e10 or -1e10 for large and small numbers respectively). The same is for bool return types, consider what true or false may imply further down the line and return the appropriate response. For collections, empty collections are appropriate.

+

The final decision for what the return should be will reside with the relevant toolkit lead, who should take into consideration the expected use cases and user stories.

+

The error message should also convey to the user which bit of the data is null and what they need to fix it. Consider the above example, the panel may not be null but the Construction property might be. Therefore panel.Construction.Name will also throw a NullReferenceException.

+

IsNull

+

For complex objects, with multiple properties to check, you may wish to implement an IsNull check query method, which takes the object and checks all of the nested data to check if any of it is null and returns a true or false and an error message if anything was null. An example of this can be seen in the Structure_Engine IsNull method which checks objects and their complex properties. This is useful for areas where the entire object must have valid data, but may not be appropriate for other instances. It is toolkit lead and developer discretion as to which way null checks should be handled in a given method.

+

Cheat Sheet

+

The following cheat sheet can be used as a guideline for what should be the default return type if a null check has failed for different types. This is not the definitive list, and many occasions may do something different with suitable justification. But if in doubt, the following can be used and would be accepted in 99 cases out of 100.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Return typeReturn value
int, decimal-1 or 0 - whichever is the most appropriate downstream
doubledouble.NaN or -1 or 0 - whichever is the most appropriate downstream
floatfloat.Nan or -1 or 0 - whichever is the most appropriate downstream
string"" or null - whichever is the most appropriate downstream
boolfalse or true - whichever is the most appropriate downstream (will depend on what the method is doing, e.g. a query for HasConstruction could return false appropriately because a null object cannot have a construction)
List or other IEnumerableEmpty list (new List<object>();) or null
Complex object (e.g. a BHoMObject such as Panel or Barnull
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Concepts/Templates/index.html b/Development/Concepts/Templates/index.html new file mode 100644 index 000000000..18aaa9e43 --- /dev/null +++ b/Development/Concepts/Templates/index.html @@ -0,0 +1,4305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM coding templates - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM coding templates

+

Visual studio template files have been set up to help guide and simplify the development process of the BHoM.

+

The currently available templates are:

+
    +
  • Toolkit Template. You can use this to create a scaffolded Visual Studio solution ready for the development of a Toolkit. It includes an Adapter, an Engine and an oM project templates.
  • +
  • Engine method templates. They make it faster to to add new Engine methods to an Engine project.
  • +
+

Toolkit template

+

For more guidance on how to use the Toolkit template, please see Toolkit Template.

+

Engine method templates - add them to Visual Studio

+

To get visual studio to detect the templates follow these steps:

+
    +
  1. Download the template zip files from here.
  2. +
  3. Place the files in the visual studio templates folder. This will generally be:
  4. +
  5. C:\Users\ USERNAME \OneDrive\Documents\ VISUAL STUDIO VERSION \Templates\ProjectTemplates\Visual C# for any project template like the BHoM Adapter Template.
  6. +
  7. C:\Users\ USERNAME \OneDrive\Documents\ VISUAL STUDIO VERSION \Templates\ItemTemplates\Visual C# for any item template like Engine method templates.
  8. +
  9. Restart visual studio.
  10. +
+

When you choose New Project from the visual studio menu all project templates should now show up there and when adding a new item to an existing project should now mean all the item templates should show up.

+

Known Issues

+

If template is used to add a method by right clicking on a folder, an extra the folder name will be added. This will in many cases be wrong and conflict with the class name. Issues have been raised to improve the templates further going forward. In the meantime, please check the namespace of added methods.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Development-FAQ/index.html b/Development/Development-FAQ/index.html new file mode 100644 index 000000000..cfc37af4e --- /dev/null +++ b/Development/Development-FAQ/index.html @@ -0,0 +1,4302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Development FAQ - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Development FAQ

+ +

What does it mean when a piece of code is locked? How do I lock code?

+

A piece of code is locked when it is being developed by someone else. +You can check if some code is locked if its related issue is mentioned in the “In Progress” or “In Review” column of the BHoM Project Board.

+

You shouldn't touch code that is locked, until the current task ends or is archived. +If you urgently need that some new code to be pushed into the main stream - an important bug fix for example - reach out to the person assigned to the issue that is locking the code and speak to her/him.

+

Read the wiki pages on naming conventions and avoiding clashes for more information.

+

I am using Windows 10. Is anything different for me ?

+

If you are using a computer which runs on windows 10, you might find that when you reference dlls in a project, the path of those will be pointing to your OneDrive folder. This will obviously lead to the issue that the code will not compile for other people.

+

img

+

If this id the case, re-referencing the dlls might not solve the issue and then you will have to manually edit that in the project folder. You do this by opening the project file (.csproj) in a text-editor and you will find some of the dlls being referenced as

+

+

which you will have to replace by

+

image

+

Note that the path in visual studio will still be pointing to your OneDrive, but now the referencing will not create issues for others. +Do NOT FORGET TO COMMIT this changes!

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Testing/Data-Driven-Tests/index.html b/Development/Testing/Data-Driven-Tests/index.html new file mode 100644 index 000000000..8a1bb4165 --- /dev/null +++ b/Development/Testing/Data-Driven-Tests/index.html @@ -0,0 +1,4321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Data driven tests - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Data driven tests

+

BHoM has several ways to cover developed functionality with Tests. An automated strategy for covering possible regression (i.e. loss of functionality erroneously introduced by code changes) can be done with "Data driven tests".

+

Data driven tests are simply a way to take a "snapshot" of the input and output of a specific method. The input and output are stored in a dataset, together with the name of the method used to produce the output from the input. This data can be then used to automatically trigger the method at a later time, or periodically, to check that the method has not been broken e.g. with side-effects of other code changes elsewhere.

+

To record the test data, you simply need to run a target Engine method with some specific input data. The input data and the ouput of the method, together with the method name, will be recorded. When the data-driven test will be run, it will simply call again the method in question with the stored input data, and compare it with the output data. This way, it is possible to check that Engine methods keep behaving reliably.

+

This kind of "Data-driven Unit test" can be run automatically via CI/CD for an automated checking of the functionality.

+

Storing test data for Engine Methods

+

To store data for tests, you can use the Test_Toolkit and the Unit Test component.

+

image

+

Procedure

+
    +
  1. Compile the Test_Toolkit - it contains some useful methods that are not shipped in the BHoM installer.
  2. +
  3. Drop a Unit Test component in a script.
  4. +
  5. Right click the component. Use the search field to find and select method you want to store test data for. Once done, the component Unit test will transform into a UT:MethodName component. + For example, if you want to test the method called BaseTypes(), type and select its name. The component will transform into a UT:BaseType component. See screenshot below.
    + This component will have as many input as the selected method. What it will do is simply run the selected method with any provided input data.
  6. +
  7. Produce some input test data for the method. This data is simply some objects that the method can take as an input. Don't just do random objects: think about what kind of test data an user may want to input to the test, and about particular "edge" situations you want to make sure that work. The input data should cover as many input combinations and "particular inputs" as it makes sense to, in order to have good test coverage.
  8. +
  9. Connect the test data to the Unit Test component. The component will execute the target method with the provided data, and it will return one or more Unit Test objects, which contain the input and outputs related to the method execution.
  10. +
  11. At this point, we will want to store the Unit Test objects as a json somewhere where the automation can find them, so future testing can be done automatically. Our place of choice is the .ci folder of the repository where the method being tested can be found. + In order to do this easily and reliably, you can use the Test_Toolkit's StoreUnitTests function. Please refer to the screenshot below.
    + The StoreUnitTests function will save the test data in the .ci folder of the repository.
  12. +
+

image

+
    +
  1. Make sure to commit and push the data in your PR.
  2. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Testing/index.html b/Development/Testing/index.html new file mode 100644 index 000000000..b7df9ed17 --- /dev/null +++ b/Development/Testing/index.html @@ -0,0 +1,5066 @@ + + + + + + + + + + + + + + + + + + + + + + + + + BHoM Testing - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

BHoM Testing

+

BHoM allows to create tests of several types. We mainly distinguish between Unit/Functional Tests and Data-Driven Tests. This section explains in detail how to write Unit/Functional Tests for BHoM in Visual Studio. For Data-Driven Tests, please refer to their page; among other things, in this page you will also find a section dedicated to their comparison.

+

The main sections of this page are:

+ +

Test Solution setup

+

BHoM operates a separation between tests and functionality/object models. This is achieved by placing the tests in a different solution from the main repository solution.

+

In this page, we will make an example where we want to create tests for the Robot_Toolkit.

+

Create a new unit-tests directory

+

To add a new test solution, please create a new unit-tests folder in the Toolkit's .ci directory, e.g.:

+

image

+

If a .ci folder does not exist in your Toolkit yet, create that first.

+

Create a new test solution

+

You can create a new Test solution in Visual Studio from the File menu as shown below.

+

2023-08-22 13_16_15-Diffing_Tests - Microsoft Visual Studio

+

Search for NUnit in the search bar and select it:

+

2023-08-22 13_16_33-Diffing_Tests - Microsoft Visual Studio

+

Make sure that you have "create new solution" and "place solution and project in the same directory" toggled on.

+

Please name the new test solution with the same name as the main toolkit plus the suffix _Tests. For example, for Robot_Toolkit, the new test solution will be called Robot_Toolkit_Tests.

+

2023-08-22 13_16_47-Diffing_Tests - Microsoft Visual Studio

+

This will create a new solution with a dummy NUnit test project in it. For example, if we are setting up the Robot_Toolkit_Tests for the first time, we will end up with this:

+

image

+

Add the existing Toolkit projects to the Test solution

+

In order to reference the main Toolkit projects, you can add "Existing projects" to the test solution. This will allow debugging the Toolkit code while running the unit tests.

+

Right-click the solution name in the Solution Explorer and do "Add existing project":

+

image

+

Navigate to the Toolkit's repository and select the Toolkit's oM project, if it exists:

+

image

+

This will add the Toolkit's oM project to the Test solution.

+

Repeat for all the Toolkit's projects, e.g. the Engine and Adapter ones, if they exist. In the example for the Robot_Toolkit, you will end up with this:

+

image

+

Add a Solution Configuration for more efficient testing

+

After adding the Toolkit's existing projects to the Test solution, you can add a new "Test" Solution Configuration that can be used when running tests.

+

Doing this allows to avoid time-consuming situations, like when you need to close software that locks the centralised assemblies (e.g. Rhino Grasshopper, Excel) whenever you want to compile or run Unit Tests. This is because BHoM relies on post-Build Events to copy assemblies in the ProgramData/BHoM folder, and if a software locks them, the project cannot build successfully.

+

Go in the Configuration Manager as below:

+

image

+

Then select "New":

+

image

+

And do the following:

+

image

+

This will create a new Solution Configuration called "Test". Make sure it's always selected when running tests from the Test solution:

+

image

+

In order to get the benefits from this, we will need to edit the Post-build events of every non-test project in the Toolkit (in our example for the Robot_Toolkit, these are only 3: the Robot_oM, the Robot_Engine, and the Robot_Adapter). Let's take the example of Robot_oM. The post-build events can be accessed by right-clicking the project, selecting Properties, then looking for "Post-build Events".

+

image

+

The post build events should look something like this: +

xcopy "$(TargetDir)$(TargetFileName)" "C:\ProgramData\BHoM\Assemblies" /Y
+

+

This instructs the MSBuild process to copy the compiled assembly to the BHoM central folder, from where they can be loaded by e.g. UIs like Grasshopper. We do not want this copy process to happen when we are only testing via NUnit. Therefore, we can modify the post build event by replacing it with:

+
if not "$(ConfigurationName)" == "Test" (xcopy "$(TargetDir)$(TargetFileName)" "C:\ProgramData\BHoM\Assemblies" /Y)
+
+

This means that the post-build event is going to be triggered only when the Solution Configuration is not set to "Test".

+
+

Solution Configuration

+

Make sure that the Solution Configuration is always set to "Test" when you are in the Test solution (e.g. GitHub/Robot_Toolkit/.ci/unit-tests/Robot_Toolkit_Tests.sln) and not selected when you are in the normal toolkit solution (e.g. GitHub/Robot_Toolkit/Robot_Toolkit.sln).

+

If you have followed the guide so far, this will work fine.

+

The only thing that this changes is that the DLLs are not copied in the BHoM central location if the "Test" configuration is selected: in you are developing some new functionalty and you want the change to appear in e.g. a UI like Grasshopper, you need to make sure to compile the solution with the "Debug" configuration!

+
+

Create a new a test project

+

At this point, you should have a Test solution .sln file in your Toolkit's .ci folder, e.g. something like GitHub/Robot_Toolkit/.ci/unit-tests/Robot_Toolkit_Tests.sln.
+You will now want to create a Test project where we can write tests.

+

Decide what the Test project should target

+

In order to create a new test project, you should decide what kind of functionality you will want to test there. Because BHoM functionality only resides in Engine and Adapter projects (not oM projects), we can have one test project corresponding to each Engine/Adapter project.

+

For example, say you want to write tests to verify the functionality that is contained in some Robot_Engine method, for example, Robot.Query.GetStringFromEnum(). Because this method resides in the Robot_Engine, we will need to place it into a Test project that is dedicated to testing Robot_Engine functionality.

+

We can create a new test project for this. Right-click on the Solution in the Solution Explorer and do "Add" and then "New Project":

+

image

+

Search for NUnit in the search bar and select it:

+

image

+

Because this test project will target functionality in the Robot_Engine, let's name it appropriately as Robot_Engine_Tests:

+

image

+

Click next and accept .NET 6.0 as the target framework, then click "Create".

+

image

+

We will end up with this new test project:

+

image

+

We can also delete the dummy test project at this point. Right-click the Robot_Toolkit_Test project and do "remove":

+

image

+

We end up with this situation:

+

image

+

Configure the default namespace for the test project

+

We want to set up the default namespace for tests included in this project. To do so, right-click the test project and go in Properties:

+

image

+

Type "default namespace" in the search bar at the top, then replace the text into the text box with an appropriate namespace. The convention is: start with BH.Tests., then append Engine. or Adapter. depending on what the test project tests will target; then end with the name of the software/toolkit that the project targets, for example Robot. For our example so far, we will have BH.Tests.Engine.Robot.

+

image

+

Adding references to a Test Project

+

Add existing project references

+

Because the test will verify some functionality placed in another project, namely the Robot_Engine, we need to add a reference to it. Right-click the project's dependencies and do "add project reference":

+

image

+

Then add the target project and any upstream dependency to the target project. For example, if adding an Engine project, make sure you add also the related oM project; if adding an Adapter project, add both the related Engine and oM projects.

+

image

+

Add other BHoM assemblies dependencies

+

Most likely you will need to reference also other assemblies in order to write unit tests. Again, right-click the project's dependencies and do "add project reference", then click on "Browse" and "Browse" again:

+

image

+

This will open a popup. Navigate to the central BHoM installation folder, typically C:\ProgramData\BHoM\Assemblies. Add any assembly that you may need. These will appear under the "Assemblies" section of the project's Dependencies.

+

Typically, a structural engineering Toolkit will need the following assembly references, although they will vary case by case:

+

image

+

Once you have added the assemblies, please select all of them as in the image above (click on the top one, then shift+click the bottom one) and then right click on one of them. Select "Properties" and under "Copy Local" make sure that "True" or "Yes" is selected:

+

image

+

This is required to make sure that NUnit can correctly grab the assemblies.

+

Adding extra NuGet packages

+

We can leverage some other NuGet packages to make tests simpler and nicer.

+

If you want your Unit test to be automatically invocable by CI/CD mechanisms, you should check with the DevOps lead if the NuGet packages you want to use are already supported or can be added to the CI/CD pipeline. The following packages are already supported.

+

Add FluentAssertions

+

We use the FluentAssertions NuGet package for easier testing and logging. +Please add it by right-clicking the Project's Packages and do "Manage NuGet packages":

+

image

+

Click "Browse", then type "FluentAssertions" in the search bar. Select the first result and then click "Install":

+

image

+

We will provide some examples on how to use this library below. Please refer to the FluentAssertions documentation to see all the nice and powerful features of this library.

+

Writing tests

+

Let's image we want to write some test functionality for the Robot Query method called Robot.Query.GetStringFromEnum(). Because this method resides in the Robot_Engine, we will need to place it into the Robot_Engine_Tests project (created as explained above).

+

Because the method we want to test is a Query method, let's create a folder called Query:

+

image

+

Right-click the newly created Query folder and do Add new Item:

+

image

+

Let's call the new item as the method we want to test, e.g. GetStringFromEnum:

+

image

+

Let's edit the content of the generated file, so it looks like the following.

+
using NUnit;
+using NUnit.Framework;
+using FluentAssertions;
+
+namespace BH.Tests.Engine.Robot.Query
+{
+    public class GetStringFromEnumTests
+    {
+        [Test]
+        public void GetStringFromEnum()
+        {
+
+        }
+    }
+}
+
+

In particular, note that: +- we added a using NUnit;, using NUnit.Framework; and using FluentAssertions; at the top; +- we edited the name of the class appending Tests +- We added an empty test method called as the Engine method we want to verify (GetStringFromEnum). The test method is decorated with the [Test] attribute.

+

Test sections: Arrange, Act, Assert

+

Every good test should be composed by these 3 clearly identifiable main sections (please refer to Microsoft's Unit testing best practices for more info and examples):

+
    +
  • Arrange: any statement that defines the inputs and configurations required to do the verification;
  • +
  • Act: execute the functionality that we want to verify, given the Arrange setup;
  • +
  • Assert: statements that make sure that the result of the Act is as it should be.
  • +
+

The test structure should always be clear and follow this structure. Each test should only verify a specific functionality. You can have multiple assertion statements if they all concur to test the same functionality, but it can be a red flag if you have more than two or three: it often means that you should split (or parameterise) the test.

+

Your first test: a simplistic example

+

Following the example so far, we could write this code for the GetStringFromEnum() test method:

+
[Test]
+[Description("Verify that the GetStringFromEnum() method returns the correct string for a specific DesignCode_Steel enum value.")]
+public void GetStringFromEnum()
+{
+    // Arrange
+    // Set up any input or configuration for this test method.
+    var input = oM.Adapters.Robot.DesignCode_Steel.BS5950;
+
+    // Act
+    // Call the target method that we want to verify with the given input.
+    var result = BH.Engine.Adapters.Robot.Query.GetStringFromEnum(input);
+
+    // Assert
+    // Make sure that the result of the Act is how it should be.
+    result.Should().Be("BS5950");
+}
+
+

Note that we use FluentAssertions' Should().Be() method to verify that the value of the result is equal to the string BS5950, as it is supposed to be when calling the GetStringFromEnum engine method with the input DesignCode_Steel.BS5950.

+

Also note that a good practice is to add a test [Description] too! This is very helpful in case the test fails, so you get an explanation of what kind of functionality verification failed and what how it was supposed to work.

+
+

Why this is a bad example of unit test

+

This example is simplistic and shown for illustrative purposes. It's not a good unit test for several reasons:

+
    +
  • we are not testing every possible combination of inputs to the GetStringFromEnum() engine method and related outputs.
  • +
  • it hard-codes the value BS5950. We took that value by copying it from the body of the GetStringFromEnum() method and putting it in the Assert statement. This effectively duplicates that value in two places. If the string in the engine method was modified, you would need to modify the test method too. You should avoid this kind of situation and limit yourself to verifying things variables defined as part of the "Arrange" step. If you need to verify multiple output value possibilities, you should be using a Data-Driven approach.
  • +
+

See below for better examples of unit tests.

+
+

Better examples of tests

+

To illustrate good unit tests, let's look at another repository, the Base BHoM_Engine. Let's look at the test in the IsNumericIntegralTypeTests class, which looks like this (edited and with additional comments for illustrative purposes):

+
namespace BH.Tests.Engine.Base.Query
+{
+    public class IsNumericIntegralTypeTests
+    {
+        [Test]
+        public void AreEnumsIntegral()
+        {
+            // Arrange. Set up the test data
+            var input = typeof(DOFType);
+
+            // Act. Invoke the target engine method.
+            var result = BH.Engine.Base.Query.IsNumericIntegralType(input);
+
+            // Assert. Verify that the output of the Act is how it should be.
+            // If it fails the message in the string will be returned.
+            result.ShouldBe(true, "By default, IsNumericIntegralType() considers enums as a numeric integral type.");
+        }
+
+        [Test]
+        public void AreIntsIntegral()
+        {
+            // Arrange. Set up the test data
+            var input = 10.GetType();
+
+            // Act. Invoke the target engine method.
+            var result = BH.Engine.Base.Query.IsNumericIntegralType(input);
+
+            // Assert. Verify that the output of the Act is how it should be.
+            // If it fails the message in the string will be returned.
+            result.ShouldBe(true, "Integers should be recongnised as Numeric integral types.");
+        }
+    }
+}
+
+

As you can see, this class contains 2 tests: AreEnumsIntegral() and AreIntsIntegral(). A single test class should test the same "topic", in this case the BH.Engine.Base.Query.IsNumericIntegralType() method, but it can (and should) do so with as many tests as needed.
+The first test checks that C# Enums are recognised as integers by the method IsNumericIntegralType (they should be). The second test checks that the same method also recognises C# Integers are recognised as integers.

+

Why are these tests better examples of good unit tests than the one in the previous section?

+
    +
  • Test should be "atomical" like this, because if something goes wrong, there is going to be a specific test telling you what did go wrong.
  • +
  • The possible outcomes are limited to True/False; it can be acceptable to "hard-code" True/False in the unit test itself. Writing result.ShouldBe(true) makes sense, as opposed to result.ShouldBe(someVerySpecificString) or result.ShouldBe(someHugeDataset).
  • +
+

A good idea would be to add a test that verifies that a non-integral numerical value is recognised as not an integer, for example a double like 0.15. Another test could be verifying that a non-numerical type is also recognised as not an integer, for example a string.

+

If the possible outcomes of the output data were not limited to True/False, the target method would have been better suited to be verified with a Data-driven test. However, in certain situations, like when doing Test Driven Development, it can be acceptable to write tests that verify complex output data, although it's likely that a full test coverage will only be reached with Data-driven tests.

+

For more examples of good tests, keep reading.

+

Unit tests VS Data-Driven VS Functional tests

+

Unit tests verify that a particular piece of code, generally a function, works as expected. The perspective of a unit test is often that of the developer who authored the target function and that wants to make sure it works properly.
+The power of unit tests comes by creating many of them that verify the smallest possible functionality with many different input combinations. You should always strive to write small, simple unit tests. Please refer to Microsoft's Unit testing best practices for more information and examples.

+

In some cases, as mentioned in the section above, the verification in a unit test may need to target a complex set of data. For example, you may want to test your method against a "realistic" set of object, for example, many different input objects that cannot be generated easily from the code itself, but that can be easily generated in e.g. Grasshopper. In these cases, you should rely on Data-driven testing. Data-driven testing provides for more robustness against changes, because it verifies that the target function always performs in the same way. If the test function needs to change, you will have to re-write also the expected output, and this procedure increases robustness.
+However, in certain situations, like when doing Test Driven Development (TDD), it can be acceptable and even extremely helpful to write tests that verify against complex data. For example, Functional tests may well rely on complex set of data, and it's common to write them when doing TDD. In this scenario, it's still likely that a full test coverage will only be obtainable by also doing some Data-driven testing.

+

Test that verify larger functionality are also possible, in which case we talk about Functional tests. Functional test often take the perspective of a user using a piece of software that does many things in the background, like Pushing or Pulling objects via a BHoM_Adapter (in the next section you can an example of this).
+Functional tests can be slow to execute and, when they fail, they do not always give good understanding of the possible causes for the failure, because they encompass many things. However, Functional tests can be very helpful to verify that large, complex pieces of functionality work as expected under precise conditions. They are also amazingly helpful when developing new pieces of functionality using the TDD approach.

+

In many cases, the best practice is to have a good balance of Unit, Functional and Data-driven tests. This comes with experience, just start with something and you'll get there!

+
+

unit test as an umbrella term

+

Sometimes, people use the term "unit tests" as an umbrella term for all kinds of tests. +This is incorrect, as the only really generic umbrella term should be "test". However, it's a common misconception that it's often done in development.
+In BHoM we mistakenly perpetrate it in a couple of places:

+
    +
  • in the setup of the Test Solution parent folder (the .ci/unit-tests folder; we should have .ci/tests)
  • +
  • in the name of the Data-Driven test component (which is called "unit test", but could be called "data driven test"). BHoM's data-driven tests are simply a type of unit test (equality assertion on the stored output data of a single method).
  • +
+
+

A Functional test example

+

Examples of Functional tests can be seen in the Robot_Adapter_Tests project. Adapter Test projects will likely contain lots of functional tests, as we care about testing complex behaviours like Push and Pull.

+

For example, see below the test PushBarsWithTagTwice() (this is slightly edited and with additional comments for illustration purposes). We test the behaviour of the Push and Pull functionality, which in the backend is composed by a very large set of function calls. The test a first set of 3 bars, then a second set of 3 bars, and all bars are pushed with the same Tag; then it verifies that the second set of bars has overridden the first set.

+
[Test]
+[Description("Tests that pushing a new set of Bars with the same push tag correctly replaces previous pushed bars and nodes with the same tag.")]
+public void PushBarsWithTagTwice()
+{
+    // Arrange. Create two sets of 3 bars.
+    int count = 3;
+    List<Bar> bars1 = new List<Bar>();
+    List<Bar> bars2 = new List<Bar>();
+    for (int i = 0; i < count; i++)
+    {
+        bars1.Add(Engine.Base.Create.RandomObject(typeof(Bar), i) as Bar);
+    }
+
+    for (int i = 0; i < count; i++)
+    {
+        bars2.Add(Engine.Base.Create.RandomObject(typeof(Bar), i + count) as Bar);
+    }
+
+    // Act. Push both the sets of bars. Note that the second set of bars is pushed with the same tag as the first set of bars.
+    m_Adapter.Push(bars1, "TestTag");
+    m_Adapter.Push(bars2, "TestTag");
+
+    // Act. Pull the bars and the nodes.
+    List<Bar> pulledBars = m_Adapter.Pull(new FilterRequest { Type = typeof(Bar) }).Cast<Bar>().ToList();
+    List<Node> pulledNodes = m_Adapter.Pull(new FilterRequest { Type = typeof(Node) }).Cast<Node>().ToList();
+
+    // Assert. Verify that the count of the pulled bars is only 3, meaning that the second set of bars has overridden the first set of bars.
+    pulledBars.Count.ShouldBe(bars.Count, "Bars storing the tag has not been correctly replaced.");
+
+    // Assert. Verify that the count of the pulled nodes is only 6, meaning that the second set of bars has overridden the first set of bars.
+    pulledNodes.Count.ShouldBe(bars.Count * 2, "Node storing the tag has not been correctly replaced.");
+}
+
+

Leveraging the NUnit test framework: setup and teardown

+

When writing unit tests, you should leverage the NUnit test framework and other libraries in order to write clear, simple and understandable tests.

+

You may want to define NUnit "startup" methods like [OneTimeSetup] or [Setup] in order to execute some functionality when a test starts, for example starting up an adapter connection to a software. Similarly, you can define "teardown" methods to define some functionality that must be executed when a test finishes, for example closing some adapter connection.

+

Please refer to the NUnit guide to learn how to define startup and teardown methods.

+

For example, we defined such methods for the Robot_Adapter_Tests test project. Let's look at the OneTimeSetup done in Robot_Adapter_Tests:

+

namespace BH.Tests.Adapter.Robot
+{
+    public class PushTests
+    {
+        RobotAdapter m_Adapter;
+
+        [OneTimeSetUp]
+        public void OntimeSetup()
+        {
+            m_Adapter = new RobotAdapter("", null, true);
+            //... more code ...
+        }
+
+        //... more code ...
+    }
+}
+
+Here, we use the OneTimeSetup method to define a behaviour that should be executed only once before the tests contained in the class PushTests are run. This behaviour is the initialization of the RobotAdapter, which is stored in a variable in the class. All tests are going to reuse the same RobotAdapter instance, avoiding things like having to re-start Robot for each and every test, which would be time-consuming.

+

Check the Robot_Adapter_Tests test project for more examples of Setup and Teardown methods, and refer to the NUnit guide for more examples and info.

+

Running tests

+

All tests existing in a Test solution can be found in the Test Explorer. If you can't find the Test Explorer, use the search bar at the top and type "Test Explorer":

+

image

+

You can run a single test by right-clicking the test and selecting Run or Debug. If you choose "debug", you will be able to hit break points placed anywhere in the code.

+

By running tests often, you will be able to quickly develop new functionality while making sure you are not breaking any existing functionality.

+

Test Driven Development (TDD)

+

A good practice is Test Driven Development (TDD), which consists in writing tests first, and implement the functionality in the "Act" step later. You can create a stub of the implementation that does nothing, write the tests that should verify that it works fine, and then develop the functionality by adding code to the body of the stub. In other words:

+
    +
  1. Write one or, better, many tests that verify a piece of functionality. They should have Arrange, Act and Assert phases.
  2. +
  3. +

    In the "Act" phase, just write a function call to the new function you want to define. Get inspired by the Arrange step to define the signature of the function call. Don't be bothered by the compiler complaining that the function doesn't exist!

    +

    [Test]
    +public void ()
    +{
    +    // Arrange
    +    var input = someData; // data that I know I will want to use.
    +
    +    // Act
    +    // DoSomething() does't exist yet!
    +    var output = BH.Engine.Something.Compute.DoSomething(input); 
    +
    +    // Assert
    +    output.Should().Be(expectedValue);
    +}
    +
    +2. Write a stub for the target function: +
    public partial class Compute
    +{
    +    public static object DoSomething(object input)
    +    {
    +        // You will implement this later. Don't do anything.
    +        return null;
    +    }
    +}
    +

    +
  4. +
  5. +

    Run the tests. Make sure they all fail! Add as many tests you can think of: they should describe well the functionality you want to develop.

    +
  6. +
  7. Write the target function until all the tests pass!
  8. +
+

Doing this allows focusing on the "what" first, and the "how" later.
+It helps to focus on the requirements and the target result that you want to achieve with the new function. In many cases, the implementation will then almost "write itself", and you will also end up with a nice collection of unit tests that can be re-run later to verify that everything keeps working (regression testing).

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/2022-Q1---Reflection-oM-Engine-migration-to-Base-oM-Engine/index.html b/Development/Versioning/2022-Q1---Reflection-oM-Engine-migration-to-Base-oM-Engine/index.html new file mode 100644 index 000000000..4c062b9c3 --- /dev/null +++ b/Development/Versioning/2022-Q1---Reflection-oM-Engine-migration-to-Base-oM-Engine/index.html @@ -0,0 +1,4613 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 2022 Q1 Reflection oM Engine migration to Base oM Engine - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

2022 Q1 Reflection oM Engine migration to Base oM Engine

+ +

Following the discussion in this issue and associated discussions in this issue and this issue, the Reflection oM was removed from BHoM, and significant changes made to the location of methods between Reflection_Engine and BHoM_Engine DLLs.

+

Reflection_oM has been removed entirely, while Reflection_Engine has been modified. Moving forward, Reflection_Engine will house methods which allow the code base to ask questions about itself, following the traditional route of Reflection in programming, so the engine will continue to exist, but core methods that are more commonly used for general operation of the eco-system have been migrated to the Base_Engine.

+

Pull Requests

+

To jump straight into the code changes, see these PRs:

+ +

Further changes were made to all repositories within the installer. A full list is available in the following files. These links will take you to the commit states at the time this work was done, and will highlight which repositories received the updates at the time. All repositories received the updates described in this article to ensure they could compile against the base changes, with no other changes provided during this work.

+ +

Reflection_oM -> Attributes

+

BH.oM.Reflection.Attributes -> BH.oM.Base.Attributes

+

The biggest impact to repositories was via the migration of all Reflection_oM objects to the BHoM project, falling under the Base namespace. This included Attributes, Debugging, and the interfaces for MultiOutput objects.

+

The Attributes are a key part of BHoM documentation, providing Input, Output, and MultiOutput documentation attributes, as well as versioning attributes such as ToBeRemoved and PreviousVersion.

+

Prior to this work, they were housed under the namespace BH.oM.Reflection.Attributes, but this has now become BH.oM.Base.Attributes following the migration. Updating your using statements and referencing BHoM.dll rather than Reflection_oM.dll should be sufficient to resolve compilation issues here.

+

Reflection_oM -> Debugging

+

BH.oM.Reflection.Debugging -> BH.oM.Base.Debugging

+

For anyone needing to use the Debugging objects of BHoM (such as Event), these are now housed in the BH.oM.Base.Debugging namespace. Existing uses of this should be sufficient to rename the using statement and ensure a reference to BHoM.dll rather than Reflection_oM.dll.

+

Reflection_oM -> Output

+

BH.oM.Reflection -> BH.oM.Base (BH.oM.Reflection.Output<T> -> BH.oM.Base.Output<T>)

+

The Output<T> objects were housed in the top level of the Reflection_oM in the namespace BH.oM.Reflection. These have been moved to the top level of the BHoM in the namespace BH.oM.Base.

+

Anyone using Output<T, Tn> objects should find it sufficient to replace using BH.oM.Reflection; with using BH.oM.Base; and ensuring a reference to BHoM.dll rather than Reflection_oM.dll going forward.

+

Reflection_Engine -> Loading/Reflecting Assemblies

+

BH.Engine.Reflection -> BH.Engine.Base

+

These methods were primarily used by UIs to load DLLs appropriately into their platforms. These have moved to the Base Engine, in the BHoM_Engine.dll reference. Adding a reference to BHoM_Engine.dll and updating using statements and method calls should be sufficient.

+

The use of the name Reflect has been removed from the Base Engine to avoid confusion with the ongoing use of Reflection_Engine, and has become Extract. See this file for more information.

+

Reflection_Engine -> Error/Warning/Note recording

+

BH.Engine.Reflection -> BH.Engine.Base

+

BH.Engine.Reflection.Compute.RecordError() -> BH.Engine.Base.Compute.RecordError()

+

BH.Engine.Reflection.Compute.RecordWarning() -> BH.Engine.Base.Compute.RecordWarning()

+

BH.Engine.Reflection.Compute.RecordNote() -> BH.Engine.Base.Compute.RecordNote()

+

Another big change with the migration is the housing of methods related to the logging system within BHoM. These have been updated as above, with the same functionality as before. If your code was using the logging system, updating Reflection to Base and ensuring a reference to BHoM_Engine.dll should be sufficient.

+

Questions/Issues

+

If you encounter any problems following this migration, please reach out with discussion or issues as appropriate 😄

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/Backwards-compatibility/index.html b/Development/Versioning/Backwards-compatibility/index.html new file mode 100644 index 000000000..59c5bf1f9 --- /dev/null +++ b/Development/Versioning/Backwards-compatibility/index.html @@ -0,0 +1,4400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Backwards compatibility - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Backwards compatibility

+ +

Backwards compatibility

+

The two major subjects for backwards compatibility concerns methods/components and the objects/data itself.

+

Methods/Components

+

Only time these should have to break is when a parameter has been updated. This will in the long run be covered by the Version_Engine. See object name or namespace changed.

+

For all other cases the developer is responsible for ensuring that they never update public methods in a manner that can cause a script to break. Updates to methods will lead to scripts breaking if the interface of the method has been updated, which will be the case if at least one of the following is true:

+
    +
  1. Method name is changed
  2. +
  3. Method is moved to a different class
  4. +
  5. Method namespace is changed
  6. +
  7. Return type is changed
  8. +
  9. Input parameters are changed, in type, number or order
  10. +
+

If none of the above holds true for the change being made, i.e. the change only concerns the body of the method, the change is free to do without any additional concern about versioning. (Obviously any fundamental change to the behavior of the method needs normal due care and documentation.)

+

If any of the above holds true the following process should be applied:

+
    +
  1. +

    Implement the new method without removing the old.

    +
  2. +
  3. +

    Put a Deprecated tag on the old method you want to update. In the tag link over to the new method.

    +
  4. +
  5. +

    The method with the Deprecated tag can be removed when at least 2 minor releases have passed. (For example a method deprecated in version 2.2 should not be removed before version 2.4 at the earliest.)

    +
  6. +
+

Object models and serialised data

+

If an object schema is updated it will potentially lead to breaking previously serialized data and for some cases methods.

+

If the deserialisation from BSON or JSON fails, the Serialiser_Engine will fall back to deserialise any failing object to a CustomObject, containing all the data as keys in the CustomData.

+

To ensure that any old data is deserialised correctly to the updated object schema, methods in the Versioning_Engine will need to be implemented. Depending on the change made, different action needs to be taken as outlined below.

+
Object name or namespace changed
+

When a change has been made to the object name or namespace, a renaming method needs to be implemented in the Version_Engine, taking the previous full name as a string, including namespace (for example BH.oM.Structure.Element.Bar) and returning the new name as a string.

+

This will also be important when deserialising any method using the updated object as return type or input parameter.

+
Object definition changed
+

When the definition of an object has been changed, which could be:

+
    +
  • Adding a property
  • +
  • Removing a property
  • +
  • Change name or type of a property
  • +
+

the object will be deserialised to a CustomObject, as outline above.

+

To ensure that the object is being correctly deserialised covering the change being made, a convert method between versions needs to be implemented in the Version_Engine, taking the CustomObject as an argument and returning a new CustomObject with properties updated to match the new object schema. The Versioning_Engine will then attempt to deserialize the updated schema to the correct object.

+

The method implemented should just cover update from one version to the next (for example 2.3 -> 2.4). This will make it possible to chain the updates when an object has gone through several changes over multiple versions.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/Change-to-a-property-name/index.html b/Development/Versioning/Change-to-a-property-name/index.html new file mode 100644 index 000000000..c81526106 --- /dev/null +++ b/Development/Versioning/Change-to-a-property-name/index.html @@ -0,0 +1,4247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Change to a property name. - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Change to a property name.

+
    +
  1. In the Audience Profile Parameters object I want to change the property NumRows to Rows.
  2. +
  3. +

    Before any changes to the code I create the JSON string:

    +

    Annotation 2020-08-20 160127

    +
  4. +
  5. +

    And save a Grasshopper file with a panel containing that string.

    +

    Annotation 2020-08-20 160242

    +
  6. +
  7. +

    I make the property name change in the code:

    +

    Annotation 2020-08-20 155214

    +
  8. +
  9. +

    Create the Versioning_XX.json:

    +

    Annotation 2020-08-20 155326

    +
  10. +
  11. +

    Add it to the project.

    +
  12. +
  13. Rebuild the Audience_oM and Engine.
  14. +
  15. Rebuild the latest Versioning_Toolkit.
  16. +
  17. Open Grasshopper and the test file.
  18. +
  19. +

    Place the ToNewVersion component and pass in the JSON string of the old object.

    +

    Annotation 2020-08-20 160500

    +
  20. +
  21. +

    Check the change has occurred as expected by inspecting the output string from ToNewVersion.

    +

    Annotation 2020-08-20 160600

    +
  22. +
  23. +

    If that did not work then see below.

    +
  24. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/How-to-check-your-versioned-changes-are-working/index.html b/Development/Versioning/How-to-check-your-versioned-changes-are-working/index.html new file mode 100644 index 000000000..6772b8117 --- /dev/null +++ b/Development/Versioning/How-to-check-your-versioned-changes-are-working/index.html @@ -0,0 +1,4276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + How to check your versioned changes are working ? - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

How to check your versioned changes are working ?

+

To check the upgrade of a Property, Type or Namespace upgrade.

+
    +
  1. Before making any changes create an object from the Toolkit that will be upgraded.
  2. +
  3. Use ToJson to create a JSON string of the object.
  4. +
  5. Save a copy of that string.
  6. +
  7. Make changes to the code and add versioning.
  8. +
  9. Rebuild your Toolkit and the Versioning_Toolkit.
  10. +
  11. Use ToNewVersion to and verify the output to check the upgrade worked.
  12. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/Object-name-change-and-associated-custom-create-method/index.html b/Development/Versioning/Object-name-change-and-associated-custom-create-method/index.html new file mode 100644 index 000000000..4f639ba77 --- /dev/null +++ b/Development/Versioning/Object-name-change-and-associated-custom-create-method/index.html @@ -0,0 +1,4359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Object name change and associated custom create method - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Object name change and associated custom create method

+

In the Audience_oM I want to change the object name for ProfileParameters to TierProfileParameters. There are two Create methods that will also need to be upgraded. This page describes the steps to achieve that.

+

First I am going to set up some files and data to help with the process

+
    +
  1. Capture the JSON string of the object to change as described here.
  2. +
  3. +

    Set up a simple file with the auto generated object create method component and related methods that the changes will impact:

    +

    Annotation 2020-08-21 121114 +1. Use the VersioningKey component to get the string that will later be used for the the PreviousVersion Attribute that I will add to the affected methods.

    +

    Annotation 2020-08-21 121421 +1. Copy the output of VersioningKey and paste into a text editor.

    +

    Change the code to change the object name

    +
      +
    1. Change the object name and the file name for this object.
    2. +
    3. In the Engine and oM projects replace all instances of the old name with the new name.
    4. +
    +

    Annotation 2020-08-21 115657 +I'm using find and replace for the renaming - care should be taken here.

    +
  4. +
  5. +

    Check the solution builds.

    +
  6. +
  7. Create and add the versioning JSON file to the project. See here for the content of an empty Versioning_XX.json file.
  8. +
  9. +

    Add the key value pairs to describe the ToNew and ToOld upgrade / downgrade.

    +

    Annotation 2020-08-21 120337. +1. At this we can rebuild the solution and rebuild the Versioning_Toolkit. +1. First I'll check the upgrade using the json string and ToNewVersion:

    +

    Annotation 2020-08-21 123930 +1. If this fails double check all the steps above. +1. Open Rhino and the simple test file. +1. We'll see the auto generated create method has correctly upgraded, but the others show errors:

    +

    Annotation 2020-08-21 122346

    +
  10. +
+

Change the code to change the methods

+
    +
  1. I need to add the PreviousVersion attribute to ensure the methods are upgraded.
  2. +
  3. The text we saved earlier looks like: +
    BH.Engine.Audience.Create.ProfileParameters(System.Double)
    +BH.Engine.Audience.Create.ProfileParameters(System.Double, System.Double, System.Double, System.Double, System.Int32, System.Double, 
    +BH.oM.Humans.ViewQuality.EyePositionParameters, BH.oM.Audience.PlatformParameters)
    +
  4. +
  5. +

    I'll use the first of those two as arguments to the PreviousVersion attribute which will be added to the first method like this:

    +

    Annotation 2020-08-21 123225 +1. And adding the PreviousVersion attribute to the second method with more arguments will look like this:

    +

    Annotation 2020-08-21 123439

    +
  6. +
  7. +

    For compliance I will also change the name of the file containing those methods to match the renamed object type they return TierProfileParameters.

    +
  8. +
  9. +

    We can now rebuild the solution and the Versioning_Toolkit and check again if this has worked.

    +

    Annotation 2020-08-21 124131

    +
  10. +
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/index.html b/Development/Versioning/index.html new file mode 100644 index 000000000..6d2c3e776 --- /dev/null +++ b/Development/Versioning/index.html @@ -0,0 +1,4343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + What is BHoM versioning? - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

What is BHoM versioning?

+

BHoM versioning provides a system to correctly load a method or component stored in a script that has had its code changed.

+

Why is Versioning needed?

+

When you save a script that contains BHoM stuff, all of the BHoM components save information about themselves so they can initialise properly when the script is re-opened. This information is about things like the component/method name, its inputs and outputs types and names; the information is simply stored in a text (Json serialised).

+

If someone changes a BHoM method or object that was stored in a script, upon reopening of the script it will be impossible to reload that same method or object: the method initialisation will fail and the old component in the script will throw a warning or error, unable to work.

+

Versioning fixes this by updating the old json text before using it to find the method.

+

What does BHoM versioning support?

+

BHoM versioning supports the following changes:

+
    +
  • +

    Changes to methods (e.g. saved in a script):

    +
      +
    • changes in the method name
    • +
    • changes in their input/outputs names and types.
    • +
    +
  • +
  • +

    Changes to Namespaces:

    +
      +
    • renaming namespaces
    • +
    • general modification to namespaces
    • +
    +
  • +
  • +

    Changes to classes (object types):

    +
      +
    • changes in class properties
    • +
    • changes in their name
    • +
    • complex structural changes
    • +
    +
  • +
  • +

    Changes to Datasets:

    +
      +
    • renaming or moving of location
    • +
    • deletion
    • +
    +
  • +
+

Ok, tell me how to do versioning for my changes! 🚀

+

To implement versioning when you do your changes, see Versioning guide.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/technical-background-on-versioning/index.html b/Development/Versioning/technical-background-on-versioning/index.html new file mode 100644 index 000000000..7e30ddbce --- /dev/null +++ b/Development/Versioning/technical-background-on-versioning/index.html @@ -0,0 +1,4417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Technical background on Versioning - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Technical background on Versioning

+

If you want to know about how the upgrader does its job, this section is for you. Otherwise, feel free to skip it.

+

How does BHoM Versioning work?

+

Alongside the dlls installed in AppData\Roaming\BHoM\Assemblies, you can find in the bin sub-folder a series of BHoMUpgrader exe programs. When a type/method/object fails to deserialise from its string representation (json), those upgrader are called to the rescue.

+

Every quarter, when we release a new beta installer, we also produce a new upgrader named BHoMUpgrader with the version number attached at the end (e.g. BHoMUpgrader32 for version 3.2). That upgrader contains all the changes to the code that occurred during the quarter.

+

When deserialisation fails in the BHoM, the BHoM version used to serialise the object is retrieved from the json. The json is then upgraded to the following version repeatedly until it reaches the current version where it can finally be deserialised into a BHoM object.

+

image

+

Decentralisation of the upgrade information

+

We will go in details on how the upgrade information is stored inside an upgrader in the remaining sections. There is however one aspect worth mentioning already. Once a quarter is finished, an upgrader is never modified again and simply redistributed alongside the others. During that quarter however, the current upgrader is constantly updated to reflect the new changes. For everyone working on the BHoM to have to modify the exact same files inside the Versioning_Toolkit would be inconvenient and a frequent source of clashes. For that reason, the information related to the upgraded of the current quarter are stored locally at the root of each project where the change occurred.

+

image

+

Notice that the file name ends with the version of the BHoM it applies to.

+

The content of an empty Versioning_XX.json file is as follow:

+
{
+  "Namespace": {
+    "ToNew": {
+    },
+    "ToOld": {
+    }
+  },
+  "Type": {
+    "ToNew": {
+
+    },
+    "ToOld": {
+    }
+  },
+  "Property": {
+    "ToNew": {
+    },
+    "ToOld": {
+    }
+  },
+  "MessageForDeleted": {
+  },
+  "MessageForNoUpgrade": {
+  }
+}
+
+

When the UI_PostBuild process that copies all the BHoM assemblies to the Roaming folder is ran (i.e. when BHoM_UI is compiled), the information from all the Versioning_XX.json files is collected and compiled in to a single json file copied to the roaming folder next to the BHoMUpgrader executable. It's content will look similar to the local json files with an extra section for the methods (more onto that later):

+
{
+  "Namespace": {
+    "ToNew": {
+      "BH.Engine.XML": "BH.Engine.External.XML",
+      "BH.oM.XML": "BH.oM.External.XML"
+    },
+    "ToOld": {
+      "BH.Engine.External.XML": "BH.Engine.XML",
+      "BH.oM.External.XML": "BH.oM.XML"
+    }
+  },
+  "Type": {
+    "ToNew": {
+      "BH.oM.Base.IBHoMFragment": "BH.oM.Base.IFragment",
+      "BH.oM.Adapters.ETABS.EtabsConfig": "BH.oM.Adapters.ETABS.EtabsSettings", 
+    },
+    "ToOld": {
+      "BH.oM.Base.IFragment": "BH.oM.Base.IBHoMFragment",
+      "BH.oM.Adapters.ETABS.EtabsSettings":"BH.oM.Adapters.ETABS.EtabsConfig" 
+    }
+  },
+  "Method": {
+    "ToNew": {
+        "BH.Adapter.XML.XMLAdapter(BH.oM.Adapter.FileSettings, BH.oM.XML.Settings.XMLSettings)": {
+            "_t": "System.Reflection.MethodBase", 
+            "TypeName": "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.Adapter.XML.XMLAdapter, XML_Adapter, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null\" }",
+            "MethodName": ".ctor",
+            "Parameters": [ "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.oM.Adapter.FileSettings\" }" ]
+        },
+        "BH.Engine.Geometry.Compute.ClipPolylines(BH.oM.Geometry.Polyline, BH.oM.Geometry.Polyline)": {
+            "_t": "System.Reflection.MethodBase",
+            "TypeName": "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.Engine.Geometry.Compute, Geometry_Engine, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null\" }",
+            "MethodName": "BooleanIntersection",
+            "Parameters": [ "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.oM.Geometry.Polyline\" }", "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.oM.Geometry.Polyline\" }", "{ \"_t\" : \"System.Type\", \"Name\" : \"System.Double, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" }" ]
+        }
+    },
+    "ToOld": {
+
+    }
+  },
+  "Property": {
+    "ToNew": {
+        "BH.oM.Structure.Elements.Bar.StartNode": "BH.oM.Structure.Elements.Bar.Start",
+        "BH.oM.Structure.Elements.Bar.EndNode": "BH.oM.Structure.Elements.Bar.End"
+    },
+    "ToOld": {
+        "BH.oM.Structure.Elements.Bar.Start": "BH.oM.Structure.Elements.Bar.StartNode",
+        "BH.oM.Structure.Elements.Bar.End": "BH.oM.Structure.Elements.Bar.End",
+    }
+  },
+  "MessageForDeleted": {
+  },
+  "MessageForNoUpgrade": {
+  }
+}
+
+

How does the upgrader work?

+

The diagram below show the chains of calls between the 3 main upgrade methods: +- UpgradeMethod +- UpgradeType +- UPgradeObject

+

Note that UpgradeType is actually covering both the namespace replacement and the type name replacement. The reason behind it is that they come down to the same string replacement principles both at the beginning of an item full name (since types include their namespace in their full name too).

+

Also note that those three are the 3 places where an older upgrader can be called if needed.

+

image

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Development/Versioning/versioning-guide/index.html b/Development/Versioning/versioning-guide/index.html new file mode 100644 index 000000000..f0ec8e31a --- /dev/null +++ b/Development/Versioning/versioning-guide/index.html @@ -0,0 +1,5098 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Versioning guide: implementing versioning for your changes - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Versioning guide: implementing versioning for your changes

+

Versioning can be implemented in one or two ways, depending on the situation:

+
    +
  1. by adding the required information to a Versioning_XX.json file; and/or
  2. +
  3. by adding a PreviousVersion attribute to your changed method.
  4. +
+

The choice of the appropriate one depends on the change you are doing, as explained in detail in the following sections.

+

Supported changes

+

BHoM Versioning supports:

+
    +
  • +

    Changes to methods (e.g. saved in a script):

    +
      +
    • changes in the method name
    • +
    • changes in their input/outputs names and types.
    • +
    +
  • +
  • +

    Changes to Namespaces:

    +
      +
    • renaming namespaces
    • +
    • general modification to namespaces
    • +
    +
  • +
  • +

    Changes to classes (object types):

    +
      +
    • changes in class properties
    • +
    • changes in their name
    • +
    • complex structural changes
    • +
    +
  • +
  • +

    Changes to Datasets:

    +
      +
    • renaming or moving of location
    • +
    • deletion
    • +
    +
  • +
+

Head to the section below that is the most relevant to your case.

+

Changes to methods

+

This section addresses how to do Versioning for code changes done to methods, which are probably the most common. There are two possibilites here, and the first is simpler and to be preferred. Both options apply to either method renamings and/or changes in method inputs.

+

Via the PreviousVersion attribute

+

We recommend to simply add a PreviousVersion attribute on top of the method you are modifying. This attribute takes two arguments:

+ +

Example: PreviousVersion for a method renaming

+

In this example, a method whose full name was FilterFamilyTypesOfFamily, located in the namespace BH.Engine.Adapters.Revit and hosted under the static class Create, is renamed to FilterTypesOfFamily.

+
+

Versioning using the PreviousVersion attribute for a method being renamed

+
public static partial class Create
+{
+    [PreviousVersion("3.2", "BH.Engine.Adapters.Revit.Create.FilterFamilyTypesOfFamily(BH.oM.Base.IBHoMObject)")]
+    [Description("Creates an IRequest that filters Revit Family Types of input Family.")]
+    [Input("bHoMObject", "BHoMObject that contains ElementId of a correspondent Revit element under Revit_elementId CustomData key - usually previously pulled from Revit.")]
+    [Output("F", "IRequest to be used to filter Revit Family Types of a Family.")]
+    public static FilterTypesOfFamily FilterTypesOfFamily(IBHoMObject bHoMObject)
+    {
+        //....
+    }
+
+
+

Example: PreviousVersion for a method's inputs change

+

In this example, a method inputs are being changed: an input (the second one) is being removed.
+The method in the example is a constructor, but the same example applies to any method. Constructors are rarely used in BHoM – we prefer Create Engine methods, which get exposed to UIs – but some types, in particular BHoM_Adapter implementations, make use of them.

+
+

Versioning using the PreviousVersion attribute for a method whose inputs are being changed

+
public partial class XMLAdapter : BHoMAdapter
+{
+    [PreviousVersion("3.2", "BH.Adapter.XML.XMLAdapter(BH.oM.Adapter.FileSettings, BH.oM.XML.Settings.XMLSettings)")]
+    [Description("Specify XML file and properties for data transfer")]
+    [Input("fileSettings", "Input the file settings to get the file name and directory the XML Adapter should use")]
+    [Input("xmlSettings", "Input the additional XML Settings the adapter should use. Only used when pushing to an XML file. Default null")]
+    [Output("adapter", "Adapter to XML")]
+    public XMLAdapter(BH.oM.Adapter.FileSettings fileSettings = null)
+    {
+        //....
+    }
+
+
+

Via the versioning json file

+

This alternative is trickier and not required in most cases.

+

The way to do it is to provide a Method section in the VersioningXX.json file:

+
    +
  • Add a VersioningXX.json file to the project, if it does not yet exists for the current version of BHoM, as explained here.
  • +
  • Create a Versioning key as explained here.
  • +
  • Get a representational string of the method, like this. If you are changing a constructor method, just leave the methodName input empty.
  • +
  • Add the following to the Method section of the VersioningXX.json file, as shown in the below example; make sure to place your changing method's Versioning key and representational string.
  • +
+
+

Versioning using the Versioning.json file for a method whose inputs are being changed

+
  "Method": {
+    "ToNew": {
+      "BH.Adapter.XML.XMLAdapter(BH.oM.Adapter.FileSettings, BH.oM.XML.Settings.XMLSettings)": {
+        "_t": "System.Reflection.MethodBase",
+        "TypeName": "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.Adapter.XML.XMLAdapter, XML_Adapter, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null\" }",
+        "MethodName": ".ctor",
+        "Parameters": [
+          "{ \"_t\" : \"System.Type\", \"Name\" : \"BH.oM.Adapter.FileSettings\" }"
+        ]
+      }
+    },
+    "ToOld": {
+
+    }
+  }
+
+
+

Changes to namespaces

+

This applies to the case where an entire namespace is renamed. This means all the elements inside that namespace will now belong to a new namespace.

+

To record that change:

+
    +
  • Add a VersioningXX.json file to the project, if it does not yet exists for the current version of BHoM, as explained here.
  • +
  • provide the old namespace as key and the new namespace as value to the Namespace.ToNew section of the json file.
  • +
+

In order to make the change backward compatible (i.e. to allow downgrading, i.e. to open a newer BHoM script from a machine running an older version of BHoM), you can fill the ToOld section with mirrored information.

+
+

Change in namespace

+
{
+  "Namespace": {
+    "ToNew": {
+      "BH.oM.XML":  "BH.oM.External.XML",
+    },
+    "ToOld": {
+      "BH.oM.External.XML": "BH.oM.XML",
+    }
+  },
+  ...
+}
+
+
+

Changes to objects

+

Changes to class name

+

Modifying the name of a type (i.e. of a class, an object's type) requires to:

+
    +
  • add a VersioningXX.json file to the project, if it does not yet exists for the current version of BHoM, as explained here.
  • +
  • add versioning information to the Versioning json file: provide the full name of the old type (namespace + type name) as key and the full name of the new type as value.
  • +
+

In order to make the change backward compatible (i.e. to allow downgrading, i.e. to open a newer BHoM script from a machine running an older version of BHoM), you can fill the ToOld section with mirrored information.

+

In the example below, we show how the Versioning json file looks like for two classes being renamed, respectively from DocumentBuilder to GBXMLDocumentBuilder and from XMLSettings to GBXMLSettings.

+
+

Adding information to the Versioning.json file regarding two classes being renamed

+
{
+  ...
+  "Type": {
+    "ToNew": {
+      "BH.oM.XML.Settings.XMLSettings": "BH.oM.External.XML.Settings.GBXMLSettings",
+      "BH.oM.XML.Environment.DocumentBuilder": "BH.oM.External.XML.GBXML.GBXMLDocumentBuilder"
+    },
+    "ToOld": {
+      "BH.oM.External.XML.Settings.GBXMLSettings": "BH.oM.XML.Settings.XMLSettings",
+      "BH.oM.External.XML.GBXML.GBXMLDocumentBuilder": "BH.oM.XML.Environment.DocumentBuilder"
+    }
+  }
+}
+
+
+

Changes to class property names

+

For the case where an object type (i.e. class) was only modified by renaming some of its property, we have a simple solution relying on the Versioning json file. +It requires to:

+
    +
  • add a VersioningXX.json file to the project, if it does not yet exists for the current version of BHoM, as explained here.
  • +
  • add versioning information to the Versioning json file under the Property.ToNew entry. As a key, provide the full name of the type that contains the property you are renaming (namespace + type name) followed by the old property name. The value must be the new property name.
  • +
+

In order to make the change backward compatible (i.e. to allow downgrading, i.e. to open a newer BHoM script from a machine running an older version of BHoM), you can fill the ToOld section with mirrored information.

+

In the following example, two properties of the object Bar that lives in the namespace BH.oM.Structure.Elements are being renamed repectively from StartNode to Start and from EndNode to End.

+
+

Changes in an object's property names

+
"Property": {
+    "ToNew": {
+        "BH.oM.Structure.Elements.Bar.StartNode": "Start",
+        "BH.oM.Structure.Elements.Bar.EndNode": "End"
+    },
+    "ToOld": {
+        "BH.oM.Structure.Elements.Bar.Start": "StartNode",
+        "BH.oM.Structure.Elements.Bar.End": "End",
+    }
+  }
+
+
+

Structural changes to a a class

+

What if you completely redesigned a type of object and changed the properties that define it?

+

This case cannot be solved by a simple replacement of a string and will most likely require some calculations to go from the old object to the new one. This means we need a method that takes the old object in and return the new. This fact presents two challenges:

+
    +
  • The old object definition will not exist anymore, so we cannot use that as the input of the conversion method. Instead we will use a Dictionary containing the properties for both input and output of that conversion method. The other benefit is that the upgrader will not have to depend on BHoM dlls to be able to do the conversion.
  • +
  • The conversion method needs to be compile and the upgrader needs to be able to access it. While there are ways to keep the conversion method decentralised, it is way simpler to have it in the versioning toolkit directly. This means this is the only case where you cannot just write the upgrade from your own repo. Luckily, this case is less frequent than the others.
  • +
+

So what do you need to do to cover the upgrade?

+
    +
  • First, locate the Converter.cs file int the project of the current upgrader.
  • +
  • In that file, write a conversion method with the following signature: public static Dictionary<string, object> UpgradeOldClassName(Dictionary<string, object> old).
  • +
  • In the Converter constructor, add that method to the ToNewObject Dictionary. the key is that object type full name (namespace + type name) and the value is the method.
  • +
  • If you want to cover backward compatibility, you can also write a DowngradeNewClassName method and add it to the ToOldObject dictionary.
  • +
+

Here's an example.

+
+

Structural changes to an object

+
public class Converter : Base.Converter
+{
+    /***************************************************/
+    /**** Constructors                              ****/
+    /***************************************************/
+
+    public Converter() : base()
+    {
+        PreviousVersion = "";
+
+        ToNewObject.Add("BH.oM.Versioning.OldVersion", UpgradeOldVersion); 
+    }
+
+
+    /***************************************************/
+    /**** Private Methods                           ****/
+    /***************************************************/
+
+
+    public static Dictionary<string, object> UpgradeOldVersion(Dictionary<string, object> old)
+    {
+        if (old == null)
+            return null;
+
+        double A = 0;
+        if (old.ContainsKey("A")) 
+            A = (double)old["A"];
+
+        double B = 0;
+        if (old.ContainsKey("B"))
+            B = (double)old["B"];
+
+        return new Dictionary<string, object>
+        {
+            { "_t",  "BH.oM.Versioning.NewVersion" },
+            { "AplusB", A + B },
+            { "AminusB", A - B }
+        };
+    }
+
+    /***************************************************/
+}
+
+
+

A few things to notice:

+
    +
  • You are working from a Dictionary so make sure that the properties exist before using them
  • +
  • You will also need to cast them since the dictionary values are all objects
  • +
  • Make sure to provide the new object type in the dictionary by defining the "_t" property.
  • +
+

Changes to Datasets

+

Changes to a Dataset name or location

+

Updating the path to a Dataset works in a similar manner to changes to names of types. The path to a dataset is changed the path from C:\ProgramData\BHoM\Datasets leading up to the json file has been changed in any way. This could be for example be one or more of the following:

+
    +
  • The name of the json file has been changed
  • +
  • The name of the folder or any super-folder of the json file has been changed
  • +
  • An additional folder has been added to the path
  • +
  • A folder has been removed from the path
  • +
+

When this has happened, you will need to:

+
    +
  • add a VersioningXX.json file to the project, if it does not yet exists for the current version of BHoM, as explained here.
  • +
  • add versioning information to the Versioning json file under the Dataset entries, as shown in the example below.
  • +
+

In the example below, the Versioning json file specifies the move of some structural material files to a parent folder called Structure.

+
+

Changes in a Dataset name or location

+
{
+  "Dataset": {
+    "ToNew": {
+      "Materials\\MaterialsEurope\\Concrete": "Structure\\Materials\\MaterialsEurope\\Concrete",
+      "Materials\\MaterialsEurope\\Rebar": "Structure\\Materials\\MaterialsEurope\\Rebar",
+      "Materials\\MaterialsEurope\\Steel(Grade)": "Structure\\Materials\\MaterialsEurope\\Steel(Grade)",
+      "Materials\\MaterialsEurope\\Steel": "Structure\\Materials\\MaterialsEurope\\Steel",
+      "Materials\\MaterialsUSA\\Concrete": "Structure\\Materials\\MaterialsUSA\\Concrete",
+      "Materials\\MaterialsUSA\\Steel": "Structure\\Materials\\MaterialsUSA\\Steel",
+    },
+    "ToOld": {
+      "Structure\\Materials\\MaterialsEurope\\Concrete": "Materials\\MaterialsEurope\\Concrete",
+      "Structure\\Materials\\MaterialsEurope\\Rebar": "Materials\\MaterialsEurope\\Rebar",
+      "Structure\\Materials\\MaterialsEurope\\Steel(Grade)": "Materials\\MaterialsEurope\\Steel(Grade)",
+      "Structure\\Materials\\MaterialsEurope\\Steel": "Materials\\MaterialsEurope\\Steel",
+      "Structure\\Materials\\MaterialsUSA\\Concrete": "Materials\\MaterialsUSA\\Concrete",
+      "Structure\\Materials\\MaterialsUSA\\Steel": "Materials\\MaterialsUSA\\Steel",
+    }
+  }
+}
+
+
+

When versioning Dataset the ToNew segment is required, and not optional. This is for the BHoM_UI to be able to update components linking to the Dataset.

+

The ToOldversioning of Dataset is optional, but should be done if the developer wants to ensure that the Dataset still is acessible from the same serach paths as before, for calls to the methods in the Library_Engine to still work. This could for example be to ensure the call BH.Engine.Library.Libraries("Materials\\MaterialsEurope\\Concrete") still returns the same Dataset as before the change was made. It is strongly recomended that calls like the above from code is updated at the same time as the change to the dataset is made, but generally recomended that the ToOld versioning is done to ensure calls from any UI and that code calls to the methods outside the control of the developer making the change is still functions as before.

+

Deletion of a Dataset

+

When a dataset is removed without a replacement, a message should be provided, similar to how it is done for objects and methods. For datasets this is done via the MessageForDeleted section of the Dataset part of the upgrade. Example below showcasing a case where the European concrete and rebar materials have been removed:

+
+

Removed Dataset

+
{
+  "Dataset": {
+    "ToNew": {
+    },
+    "ToOld": {
+    }
+    "MessageForDeleted": {
+      "Materials\\MaterialsEurope\\Concrete": "Clear message why this dataset has been removed. Point of contact (could be a github repository) where the user can ask questions about why this was removed.",
+      "Materials\\MaterialsEurope\\Rebar": "Clear message why this dataset has been removed. Point of contact (could be a github repository) where the user can ask questions about why this was removed.",
+    }
+  }
+}
+
+
+

Items that cannot be versioned: deletions or foundational changes

+

In some cases, an upgrade/downgrade of a method or object is simply not possible:

+
    +
  • The item was deleted without replacement
  • +
  • A replacement exists but is so different from the original that an automatic conversion is impossible.
  • +
+

In such cases, it is important to inform the user and provide them with as much information as possible to facilitate the transition to the new version of the code. You will need to:

+
    +
  • add a VersioningXX.json file to the project, if it does not yet exists for the current version of BHoM, as explained here.
  • +
  • add versioning information to the Versioning json file under the MessageForDeleted and/or MessageForNoUpgrade entries. As shown in the example below.
  • +
+
+

Items that cannot be versioned

+
{
+  ...
+  "MessageForDeleted": {
+    "BH.oM.Adapters.DIALux.Furnishing": "This object was provided to build up DIALux models within a BHoM UI, but was deemed to be unnecessary with the suitable conversions between existing Environmental objects and DIALux provided by the DIALux Adapter. To avoid confusion, this object has been removed. If further assistance is needed, please raise an issue on https://github.com/BHoM/DIALux_Toolkit/issues",
+    "BH.Engine.Grasshopper.Compute.IRenderMeshes(BH.oM.Geometry.IGeometry, Grasshopper.Kernel.GH_PreviewMeshArgs)": "The method was made internal to the Grasshopper Toolkit. If you still need to render objects, consider using one of the Render methods from BH.Engine.Representation instead",
+    "BH.Engine.Adapters.Revit.Query.Location(BH.oM.Adapters.Revit.Elements.ModelInstance)": "This method was a duplicate of GetProperty method, please use the latter instead.",
+    "BH.Engine.BuildingEnvironment.Convert.ToConstruction(BH.oM.Base.CustomObject)": "This method was providing a highly specific conversion between a specific custom data schema and Environment Materials that is no longer relevant to the workflows provided in Environments. It is advised to create materials manually using the Solid or Gas types as appropriate. For more assistance please raise an issue for discussion on https://github.com/BuroHappoldEngineering/BuildingEnvironments_Toolkit/issues",
+  },
+  "MessageForNoUpgrade": {
+    "BH.oM.Structure.Loads.BarVaryingDistributedLoad": "The object has been redefined in such a way that automatic versioning is not possible. To reinstate the objects you could try exploding the CustomObject that will have been returned and make use of the BH.Enigne.Structure.Create.BarVaryingDistributedLoadDistanceBothEnds method from the Structures_Engine. If doing this, treat DistanceFromA as startToStartDistance and DistanceFromB as endToEndDistance. Also, treat ForceA and MomentA as ForceAtStart and MomentAtStart, and ForceB and MomentB as ForceAtEnd and MomentAtEnd. If you have any issues with the above, please feel free to raise an issue at https://github.com/BHoM/BHoM_Engine/issues.",
+    "BH.Engine.Reflection.Modify.SetPropertyValue(System.Collections.Generic.List<BH.oM.Base.IBHoMObject>, System.Type, System.String, System.Object)": "Please use BH.Engine.Reflection.Modify.SetPropertyValue(object obj, string propName, object value) instead.",
+    "BH.Engine.Base.Compute.Hash(BH.oM.Base.IObject, System.Collections.Generic.List<System.String>, System.Collections.Generic.List<System.String>, System.Collections.Generic.List<System.String>, System.Collections.Generic.List<System.Type>, System.Int32)": "This method's functionality has changed deeply with respect to an older version of BHoM. Please replace this component with BH.Engine.Base.Query.Hash(), then plug the inputs as needed.",
+    "BH.Engine.Adapters.Revit.Create.ViewPlan": "This method is not available any more. To reinstate the object, please use BH.Engine.Adapters.Revit.Create(string, string) instead.",
+    "BH.oM.LifeCycleAssessment.MEPScope": "This object has been updated to include new features to enhance calculations for LifeCycleAssesment workflows. Please update the object on the canvas using the default create component to update this component. For further assistance, please raise an issue on https://github.com/BHoM/LifeCycleAssessment_Toolkit/issues",
+  }
+}
+
+
+

Obtaining a Versioning Key

+

A versioning key is like a signature identifying a method or object. +You can obtain it by using the BH.Engine.Versioning.VersioningKey() method, as explained below.

+
+

Important: get the versioning key before the change

+

You need to get the versioning key of the object/method before it was changed.
+If you have already done your code changes, no worries: you can simply commit your changes on your branch, then switch back to the develop branch and recompile, then use the BH.Engine.Versioning.VersioningKey() as explained below.

+
+

Get the key for objects

+

Use the method BH.Engine.Versioning.VersioningKey() and just provide the input declaringType, which is the Full Name of the object that you are modifying (i.e. the name of the class preceded by its namespace).

+
+

Get the Versioning key for objects

+

image

+
+

Get the key for methods

+

Use the method BH.Engine.Versioning.VersioningKey() and provide both:

+
    +
  • the input declaringType, which is the Full Name of the Query/Compute/Create/Modify/Convert class (i.e. the name of the class, preceded by its namespace) which contains the method that you are modifying;
  • +
  • the input methodName, which is the name of the method that you are modifying (in case you are renaming the method, this needs to be its name before the rename).
  • +
+
+

Get the Versioning key for methods

+

image

+
+

Adding a Versioning_XX.json file to the project

+

Adding a Versioning_XX.json file to the project is needed for certain versioning scenarios, but not all. In some cases (e.g. changes in a method) it may be sufficient to use the PreviousVersion attribute.

+

This is as simple as adding an empty json file to the project, named Versioning_XX.json, where the XX must be replaced with the current BHoM version. For example:

+

image

+

The empty file should then be immediately populated with the following content (copy-paste it!):

+
{
+  "Namespace": {
+    "ToNew": {
+    },
+    "ToOld": {
+    }
+  },
+  "Type": {
+    "ToNew": {
+
+    },
+    "ToOld": {
+    }
+  },
+  "Property": {
+    "ToNew": {
+    },
+    "ToOld": {
+    }
+  },
+  "MessageForDeleted": {
+  },
+  "MessageForNoUpgrade": {
+  }
+}
+
+

Then you can fill it in as described by the relevant "changes" section.

+

Why having a Versioning_XX.json file?

+

BHoM Versioning is implemented via a specific, stand-alone mechanism, hosted in the Versioning_Toolkit.

+

By adding a Versioning_XX.json file, the information related to code changes are stored locally in each project where the change occurred. This enables decentralisation, i.e. many people can independently code and change BHoM objects or methods in different Toolkits without the need to modify the Versioning_Toolkit, avoiding clashes.

+

Troubleshooting on Versioning

+

The upgrade doesn't happen - How can I debug ?

+

The upgrader are independent exe files so you cannot reach them by attaching to your UI process as you would normally do when debugging the BHoM. They are also hidden processed so you don't have command windows popping up when opening old scripts. In case, you need to figure out what is going on in there, you can always have those upgrade processes visible by commenting two lines of code in the Versioning_Engine (situated on the code BHoM_Engine repo):

+
    +
  • In the Versioning-Engine project, find the ToNewVersion file
  • +
  • In that file, find the GetPipe method
  • +
  • Toward the end of that method, comment out the following line:
  • +
+

process.StartInfo.UseShellExecute = false;
+process.StartInfo.CreateNoWindow = true;
+
+- recompile the solution and the BHoM_UI as usual

+

You should now have command windows popping up as soon as the upgrader are needed. You should also find the BHoMUpgrader processes in your task manager.

+

Walkthroughs on Versioning

+ + +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Coding with BHoM/Datasets/Accessing-Datasets-in-code/index.html b/Guides and Tutorials/Coding with BHoM/Datasets/Accessing-Datasets-in-code/index.html new file mode 100644 index 000000000..f24a2d5b4 --- /dev/null +++ b/Guides and Tutorials/Coding with BHoM/Datasets/Accessing-Datasets-in-code/index.html @@ -0,0 +1,4669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Accessing Datasets in code - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Accessing Datasets in code

+ +

How to access BHoM Datasets programmatically

+

Accessing various datasets, such as material or section datasets, can be useful when coding for BHoM. For example, you may need datasets when coding C# Unit Tests, or when programming some particular Engine function.

+

Access BHoM Datasets from a C# program, you need to ensure the correct dependencies are added to your project. The following steps will guide you through the process of adding the appropriate dependencies and demonstrate a few methods for accessing your desired dataset.

+

Step 1: Access Reference Manager

+

Access the Reference Manager in the C# project where you want to add the dependency.

+ +

ProjectFolderReference

+

Step 2: Browse for the DLL

+

Go to the "Browse" tab and click the "Browse" button in the bottom-right corner.

+ +

BrowseTab

+

Navigate to the BHoM assemblies folder using the File Explorer window. The folder is usually located at C:\ProgramData\BHoM\Assemblies. Select Data_oM.dll and press "Add."

+ +

addDataEngineDLL

+

Step 3: Add Dependency

+

Make sure to check the box next to Data_oM.dll in the Reference Manager window and press "OK."

+ +

CheckBoxAndOK

+

Step 4: Modify File Path

+

Open the project file of your specific C# project by double-clicking it with the left mouse button. Locate the line responsible for loading Data_oM.dll and modify the file path as shown in the image below.

+ +

ModifyPathInProjectFile

+

Step 5: Get the Dataset data

+

The following example demonstrates how to access the Section Library from BHoM, specifically the .

+

To access the library, use the Match method as shown in the example below. This returns the HE1000M section defined in the EU_SteelSectionLibrary dataset.

+
var steelSection = BH.Engine.Library.Query.Match("EU_SteelSections", "HE1000M", true, true) as ISteelSection;
+
+

The Match method takes four arguments:

+
    +
  1. Library Name: "EU_SteelSections"
  2. +
  3. Object Name: "HE1000M"
  4. +
  5. Case Sensitivity: true or false
  6. +
  7. Consider Spaces: true or false
  8. +
+

The boolean values allow you to specify whether your search should be case-sensitive and whether to consider spaces within the object name.

+

Find Existing Libraries

+

If you're unsure about the available datasets, check the BHoM_Datasets repository.

+

Under BHoM_Datasets\DataSets, you'll find multiple folders and subfolders containing numerous json files. Each json is a dataset, and each folder acts as a dataset library.

+

For example, in the folder [BHoM_Datasets repo folder]\BHoM_Datasets\DataSets\Structure\SectionProperties\EU_SteelSections you will find the following json files:

+ +

jsonFilesSteelSections

+

These .json files contain multiple objects. To extract objects from these datasets, you'll need the name of the desired object. This can be found as an attribute within the .json file. To locate these names, you can open the .json file in an editor like Visual Studio Code and search for the object name you need.

+

Compliance

+

Compliance regulations for Datasets are outlined in IsValidDataset.

+

Source

+

For users of the data to be able to verify where it is coming from, it is important to populate the Source object for the dataset. As many of the properties of the source as available should generally be populated, with an emphasis on the following:

+

Title

+

The title of the publication/paper/website/... from which the data has been taken.

+ +

An HTTP link to the source. Important to allow users of the data to easily identify where the data is coming from.

+

Confidence

+

Level of confidence both in the data source and in how well the serialised data in the BHoM dataset has been ensured to match the source. It should be noted that, independent of the confidence level on the Dataset, all Datasets distributed with the BHoM are subject to the General Disclaimer.

+

The confidence is split into 5 distinct categories, and the creator/distributor/maintainer of the dataset should always aim for the highest level of confidence achievable.

+
Undefined
+

Default value - assume no fidelity and no source.

+

Should generally be avoided when adding a new Dataset for distribution with the BHoM - one of the levels below should be explicitly defined.

+
None
+

The Dataset may not have a reliable source and/or fidelity to the source has not been tested.

+

To be used for prototype Datasets where no reliable data is available, and not for general distribution within the BHoM.

+
Low
+

The Dataset comes from an unreliable source, but the data matches the source based on initial checks.

+

For cases where no reliable source for the data type is available. Can be allowed to be distributed with the BHoM in circumstances where no reliable source can be found and the data still can be deemed useful.

+
Medium
+

The Dataset comes from a reliable source and matches the source based on initial checks.

+

For most cases the minimum required level of confidence for distribution of a Dataset with the BHoM. To reach this level of confidence, the Source object should be properly filled in, and a substantial spot checking of the data should have been made. If at all possible, maintainers of a Medium confidence level Dataset should strive to fulfil the requirements of High confidence.

+
High
+

The Dataset comes from a reliable source and matches the source based on extensive review and testing.

+

Highest level of confidence for BHoM datasets, and should generally be the aspiration for all Datasets included with the BHoM.

+

To achieve this, a clear testing procedure should generally be in place, which outlines how all of the data points in the Dataset have been checked against the source data and/or verified by other means to be correct.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Coding with BHoM/Datasets/Creation-of-Datasets/index.html b/Guides and Tutorials/Coding with BHoM/Datasets/Creation-of-Datasets/index.html new file mode 100644 index 000000000..5d57a4238 --- /dev/null +++ b/Guides and Tutorials/Coding with BHoM/Datasets/Creation-of-Datasets/index.html @@ -0,0 +1,4332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Creation of Datasets - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Creation of Datasets

+

Datasets are a way to store and distribute BHoMObjects for use by others. For example, a list of standard structural materials or section properties as well as global warming potential for various materials.

+

The data should be serialised in a Dataset object, and the relevant .csproj file in the repo, in which the Dataset is stored, should have a post build event implemented that ensures that the Dataset is copied to the C:\ProgramData\BHoM\Datasets folder. This will allow it to be picked up by the Library_Engine.

+

Generate a new dataset

+

To generate a new dataset to be used with the BHoM the following steps should be taken.

+
    +
  1. +

    Generate the objects to be stored in the new Dataset. This means creating the BHoMObject of the correct type in any of the supported UIs. See below for an example of how to create a handful of standard European steel materials in Grasshopper. Remember to give the created objects an easily identifiable name as the name is what will show up when using the data in the dropdowns. Remember that all BHoM objects should be defined in SI units.

    +

    Create Steel

    +
  2. +
  3. +

    Store the created objects in a Dataset object and give the dataset an appropriate name. This is the name for the dataset - the name that appears in the UI is described the next step.

    +

    Assign objects to dataset

    +
  4. +
  5. +

    Populate the source object and assign it to the dataset. See guidance below regarding the source.

    +

    Assign source

    +
  6. +
  7. +

    Convert the dataset object and store it to a single line json file. This is easiest done using the FileAdapter. The library engine relies on the json files to be a single line per object, while the default json output from the FileAdapter is putting the json over multiple lines. To make sure the produced json file is in the correct format for the library engine, provide a File.PushConfig with UseDatasetSerialization set to true and BeautifyJson set to false to the push command. Name the file something clearly identifiable, as the name of the file will be what is used to identify the dataset by the library engine, and will be what it is called in the UI menu.

    +

    Store json to file

    +
  8. +
  9. +

    For personal use, do one of the following:

    +
      +
    1. Place the file in the relevant subfolder of the C:\ProgramData\BHoM\Datasets folder. If no relevant subfolder already exists, a new one can be added. The folder will be used to generate the menus used to find the dataset in the menu system, and also makes a whole folder searchable using the Library method. Remember that running an installer will reset the datasets folder so for this option backup the json file, or use option ii.
    2. +
    3. Place the json file in a subfolder of a folder of your own choice and use the custom dataset folder outlined below.
    4. +
    +
  10. +
  11. For distribution of the Dataset to the BHoM community do the following:
      +
    1. Store the dataset in the appropriate repository folder:
        +
      • For a general dataset, such as standard materials etc., place the json file in an appropriate subfolder folder in BHoM_Datasets.
      • +
      • For a toolkit specific dataset put the json file in a Dataset folder in the root folder of the toolkit to host the dataset. If no such folder exist, it should be created. Make sure that the oM project in the toolkit has the following post-build event code: xcopy "$(SolutionDir)DataSets\*.*" "C:\ProgramData\BHoM\DataSets" /Y /I /E that ensures that the dataset is copied over to the C:\ProgramData\BHoM\Datasets folder.
      • +
      +
    2. +
    3. Raise a Pull request on GitHub and ask for review from relevant parties.
    4. +
    +
  12. +
+

Custom dataset folder

+

By default, the Library_Engine scans the C:\ProgramData\BHoM\Datasets for all json files and loads them up to be queryable by the UI and the methods in the library engine. This location is reset with each BHoM install to make sure all datasets are up-to-date and that any modifications or fixes correctly are applied to the data. For some cases it can be also useful to have your own datasets stored in your own folder for example on a network drive to share during work on a particular project.

+

For these reasons it is possible to get the Library_Engine to scan other folders for datasets as well. This can easily be controlled via the AddUserPath and RemoveUserPath commands that can be called from any UI. After the AddUserPath command has been run once for a particular folder, the library engine will store the information about this folder in its settings and will keep on looking in subfolders of that location for any json files to be used as dataset.

+

Add user path

+

To stop the Library_Engine from looking in this particular folder, use the RemoveUserPath command, providing a link to the folder you no longer want to be scanned by the Library_Engine.

+

Remove user path

+

Remember that the menu system of the Dataset dropdown components are built up using the subfolders, so even if only a single dataset is placed in this custom folder it might be a good idea to still put your json file in an appropriate subfolder.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Guides and Tutorials/Coding with BHoM/Diffing and Hashing/Diffing-and-Hashing-\342\200\223-guide-for-developers/index.html" "b/Guides and Tutorials/Coding with BHoM/Diffing and Hashing/Diffing-and-Hashing-\342\200\223-guide-for-developers/index.html" new file mode 100644 index 000000000..4130f3702 --- /dev/null +++ "b/Guides and Tutorials/Coding with BHoM/Diffing and Hashing/Diffing-and-Hashing-\342\200\223-guide-for-developers/index.html" @@ -0,0 +1,4377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Diffing and Hashing: guide for developers - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Diffing and Hashing: guide for developers

+

This page gives a more in-depth technical explanation about some diffing methods, and also serves as a guide for developers to build functionality on top of existing diffing code.
+See the Diffing and the Hash wiki pages for a more quick-start guide.

+

Contents

+ +

Developing Toolkit-specific diffing methods

+

The IDiffing() method is designed to be a "universal" entry point for users wanting to diff their objects; for this reason, it has an automated mechanism to call any Toolkit-specific diffing method that can is compatible with the input objects. This work similarly to the Extension Method discovery pattern that is often leveraged in many BHoM methods.

+

A Toolkit-specific Diffing method is defined as a method: +- that is public; +- whose name ends with Diffing; +- that has the following inputs: + - a first IEnumerable<object> for the past objects; + - a second IEnumerable<object> for the following objects; + - any number of optional parameters; + - a final DiffingConfig parameter (that should default to null, and be auto initialised if null within the implementation).

+

Any method that respect these criteria is discovered and stored during the assembly loading through this method. It gets invoked by the IDiffing() as explained here.

+

IDiffing() method: internal workings

+

The IDiffing method does a series of automated steps to ensure that the most appropriate diffing method gets invoked for the input objects.

+

Invoking of the Toolkit-specific diffing methods

+

The IDiffing first looks for any Toolkit-specific diffing method that is compatible with the input objects (relevant code here). This is done by checking if there is a IPersistentAdapterId stored on the objects; if there is, the namespace to which that IPersistentAdapterId object belongs is taken as the source namespace to get a compatible Toolkit-specific diffing method. For example, if the input objects own a RevitIdentifier fragment (which implements IPersistentAdapterId), then the namespace BH.oM.Adapters.Revit.Parameters is taken. This namespace, which is an .oM one, is "modified" to an .Engine one, so the related Toolkit Engine is searched for a diffing method.

+

If a Toolkit-specific diffing method match is found, that is then invoked. For example, this is how RevitDiffing() gets called by the IDiffing.
+Note that only the first matching method gets invoked. This is because we only allow to have 1 Toolkit-specific diffing method. If you have method overloading over your Toolkit-specific Diffing method (for example, because you want to provide the users with multiple choices when they choose to invoke directly your Toolkit-specific diffing method), you must ensure that all overloads are equally valid and can any can be picked by the IDiffing with the same results (like it happens for RevitDiffing(): all methods end up calling a single, private Diffing method, and additional inputs are optional, so they all behave the same if called by the IDiffing).

+

What happens for objects that do not have a Toolkit-specific diffing method

+

If the previous step does not find any Toolkit-specific diffing method compatible with the input objects, then a variety of steps are taken to try possible diffing methods. In a nutshell, a series of checks are done on the input objects to see what diffing method is most suitable. This is better described in the following diagram. For more details on each individual diffing method, see here.

+

IDiffing1

+

Other Diffing methods inner workings

+

In addition to the main Diffing method IDiffing(), there are several other methods that can be used to perform Diffing. These are a bit more advanced and should be used only for specific cases. All diffing methods can be found in the Compute folder of Diffing_Engine.

+

Most diffing methods are simply relying on an ID that is associated to the input objects, or a similar way to determine which object should be compared to which. Once a match is found, the two matched objects (one from the pastObjects set and one from the followingObjects set) are sent to the ObjectDifferences() method, as illustrated by the following diagram.

+

This diagram also illustrates that only the DiffWithHash() method does not rely on the ObjectDifferences() method. The DiffWithHash() is a rather simple and limited method, in that it cannot identify Modified objects but only new/old ones, and it is described here.

+

Diffing methods-simplified

+

ObjectDifferences() method inner workings

+

As shown above, the method that does most of the work in diffing is the BH.Engine.Diffing.Query.ObjectDifferences() method.

+

This is the method that has the task of finding all the differences between two input objects. This method currently leverages an open-source, free library called CompareNETObjects by Kellerman software. It maps our ComparisonConfig options to the equivalent class in the CompareNETObjects library, and then executes the comparison using it.

+

Mapping our ComparisonConfig to Kellerman library

+

Because not all of the options available in the ComparisonConfig are mappable to Kellerman's, ObjectDifferences() has to adopt a workaround. For example, our numerical approximation options are not directly compatible.
+The general compatibility strategy is: +- if an option is mappable/convertible, map/convert it from our ComparisonConfig to Kellerman's CompareLogic object. This is true for most of them. +- if an option is not compatible with Kellerman (like our numerical approximation options), set Kellerman CompareLogic so it finds all possible differences with regards to that option (like we do for numerical differences), then iterate the differences found and cull out those that are non relevant (example for the numerical differences).

+

The loop to iterate over the differences found by Kellerman is also useful to further customise the output, as shown by the following section.

+

Customising the Diffing output: ComparisonInclusion() extension method

+

In order to customise our diffing output, we want to customise how the ObjectDifferences() method determines the differences between objects. +This is done through a specific ComparisonInclusion() extension method that is invoked when we loop through the differences found by the Kellerman library. This is essentially an application of the Extension Method discovery pattern that is often leveraged in many BHoM methods.

+

You can implement a ObjectDifferences() method in your Toolkit to customise how the difference between two specific objects is to be considered by the diffing. This method must have the following inputs, in this order: +- a fist object input (which will be the object coming from the pastObjs set); +- a second object input, of the same type as the first object (which will be the object coming from the followingObjs set); +- a string input, which will contain the Full Name of the property difference found by the ObjectDifferences() method; +- a BaseComparisonConfig input, which will be passed in by the ObjectDifferences() method.

+

The method must return a ComparisonInclusion object, which will contain information on whether the difference should be included or not, and how to display it.

+

Here is an example of ComparisonInclusion() for RevitParameters: +

public static ComparisonInclusion ComparisonInclusion(this RevitParameter parameter1, RevitParameter parameter2, string propertyFullName, BaseComparisonConfig comparisonConfig)
+{
+    // Initialise the result.   
+    ComparisonInclusion result = new ComparisonInclusion();
+
+    // Differences in any property of RevitParameters will be displayed like this.
+    result.DisplayName = parameter1.Name + " (RevitParameter)"; 
+
+    // Check if we have a RevitComparisonConfig input.
+    RevitComparisonConfig rcc = comparisonConfig as RevitComparisonConfig;
+
+    // Other logic
+    ...
+}
+

+

Note that this method supports Toolkit-specific ComparisonConfig objects, like e.g. RevitComparisonConfig. See the section below for more details.

+

Customising the Hash: HashString() extension method

+

If you want a specific object to be Hashed in a particular way, you can implement a HashString() extension method for that object in your Toolkit. The HashString() method will get invoked when computing the Hash(). This is essentially an application of the Extension Method discovery pattern that is often leveraged in many BHoM methods.

+

This method must have the following inputs, in this order: +- An object input, which will be the object for which we are calculating the Hash. +- A string input, which will indicated the FullName of the property being analysed by the Hash() method (for example when the input object is a property of another object; this can be useful in certain cases, and if not useful can simply be ignored). +- A BaseComparisonConfig input, which can be used to will be passed in by the Hash() method.

+

Here is an example of HashString() for RevitParameters:

+
public static string HashString(this RevitParameter revitParameter, string propertyFullName = null, BaseComparisonConfig comparisonConfig = null)
+{
+    // Null check.
+    if (revitParameter == null) return null;
+
+    string hashString = revitParameter.Name + revitParameter.Value;
+
+    // Check if we have a RevitComparisonConfig input.
+    RevitComparisonConfig rcc = comparisonConfig as RevitComparisonConfig;
+
+    // Other logic
+    ...
+}
+
+

Note that this method supports Toolkit-specific ComparisonConfig objects, like e.g. RevitComparisonConfig. See the section below for more details.

+

Toolkit-specific ComparisonConfig options

+

There are cases where you may need more options to further customise the Hash or Diffing process, to refine how they work with your Toolkit's objects.

+

The "default" comparisonConfig object gives all the default options, and it inherits from the BaseComparisonConfig abstract class. This abstract class can be extended by the "Toolkit-specific" comparisonConfigs, so you can include additional options to deal with certain objects in your Toolkit.
+See an example with Revit's RevitComparisonConfig.

+

If you implement your own Toolkit-specific ComparisonConfig object, you will need to implement the functions that deal with it too, which should include at least one of: +- A toolkit-specific Diffing() method (example in Revit), which your users can call independently, or that may be automatically called by the IDiffing method, as shown here. +- A toolkit-specific HashString() method (example in Revit), which will get invoked when computing the Hash(). +- Any number of ComparisonInclusion() methods that you might need to customise the diffing output per each object (example in Revit for RevitParameters), as explained here.

+

Testing and profiling

+

We have a DiffingTests repo which contains Unit Tests and profiling functions. These are required given the amount of options and use cases that both offer.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Coding with BHoM/Getting-started-for-developers/index.html b/Guides and Tutorials/Coding with BHoM/Getting-started-for-developers/index.html new file mode 100644 index 000000000..ad7852622 --- /dev/null +++ b/Guides and Tutorials/Coding with BHoM/Getting-started-for-developers/index.html @@ -0,0 +1,4531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Getting started for developers - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Getting started for developers

+

Welcome Developers! 🚀

+

Here's a quick start guide. After reading this, you might want to head to create your own Toolkit.

+

Building BHoM from Source

+

Please follow the steps below:

+
    +
  1. Use git clone (or use GitHub desktop) to download the repositories in the list below.
  2. +
  3. Use your preferred IDE to build the solutions in the order as they appear below. +We recommend Visual Studio Community.
  4. +
+
+

Build order

+

The first time you build BHoM you need to clone and build the repos in the order specified below.

+

You must pick all the Mandatory repos.

+
+
+

Rebuilding and seeing changes in the UIs (Grasshopper/Dynamo/Excel)

+

When building in visual studio, the compiled assemblies will go in the ./Build folder of your Repo; additionally, there is a Post-Build event that copies the files in the central BHoM folder: C:\ProgramData\BHoM\Assemblies.

+

When you build, if there is any UI open (e.g. Rhino/Grasshopper/Revit/Excel), the dlls will not be overwritten in the central folder because they are referenced by the UI software. Therefore, to ensure the changes are visible in the UI, you must make sure to close all UI software, then reopen it to see updated changes.

+
+
+

Tip

+

When developing a Toolkit, in order to reduce rebuild iterations, you might want to:

+
    +
  1. Rebuild your Toolkit
  2. +
  3. Rebuild BHoM_UI
  4. +
  5. Start Debugging your Toolkit with an UI application attached.
  6. +
+

The last step will fire up your UI application and you will be able to modify the code while debugging, on-the-fly (just press the Pause button in Visual Studio).

+

Note that not all IDEs support this (notably, not the Express editions of Visual Studio – only the Community, Professional and Enterprise ones do).

+

An alternative that always works is, after steps 1 and 2 above, simply fire up your UI application and attach to its process. + This way you will be able to follow code execution and check exceptions; however, this does not allow for code modification while debugging.

+
+

Mandatory base repos

+

Main repos

+

Compile each of these, one after the other:

+ +

User interface(s)

+

Compile one or more of the following - depending on the User interface software you want to use:

+
    +
  1. Rhinoceros_Toolkit and then Grasshopper_Toolkit (requires Rhinoceros_Toolkit)
  2. +
  3. Excel_Toolkit
  4. +
  5. Dynamo_Toolkit
  6. +
+

The following repos are optional.

+

Optional base repos

+

These repos are sometimes used as stand-alone, and sometimes are also referenced by other repos.

+

You might find them useful 🚀

+
    +
  • BHoM_Datasets - makes Datasets available (some test scripts might be using them)
  • +
  • Socket_Toolkit - send messages through Sockets. Some toolkits use this.
  • +
  • Mongo_Toolkit - database connection. Some toolkits use this.
  • +
  • Versioning_Toolkit - allows retro-compatibility of components (auto upgrade to newest version).
  • +
+

Toolkits 🌍

+

Toolkits provide the connection to other software.

+

Clone and build any toolkit you want to use!

+

Some examples:

+ +

FAQ and help

+

I can't Rebuild the solution: NuGet package(s) missing error

+

Sometimes you might encounter this error. Although Visual Studio "Rebuild All" command should take care of Restoring the NuGet packages for you, to solve this just run that manually.
+Right click the solution → Restore NuGet Packages.

+

I have done some changes to my code, but when I open Grasshopper (or Dynamo, or Excel) the code still behaves as before! Why it is not updated?

+

After compiling, check that the Build was successful, by looking in the "Output" tab at the bottom of the VS interface; make sure no errors are there, and also that the Post-build event worked successfully. See the notes above.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/BHoM-Grasshopper-CSharp/index.html b/Guides and Tutorials/Coding with BHoM/IDEs/BHoM-Grasshopper-CSharp/index.html new file mode 100644 index 000000000..b42ccfa3b --- /dev/null +++ b/Guides and Tutorials/Coding with BHoM/IDEs/BHoM-Grasshopper-CSharp/index.html @@ -0,0 +1,4425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Coding BHoM in a Grasshopper CSharp script component - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Coding BHoM in a Grasshopper CSharp script component

+

BHoM can be referenced and used in a Grasshopper "C# Script" component. The only additional requirement as of the current version is to also reference netstandard.dll in the same Grasshopper component.

+

Find the .NET Standard assembly dll file

+

Currently, a reference to the .NET Standard assembly is required to use BHoM from a C# script component in Grasshopper.

+

Download (or find on your drive) the .NET Standard assembly

+

You can download the right version of netstandard.dll (currently, 2.0.3 is the one used in BHoM) from here: +1. Click on "download package". +2. Open the downloaded .npckg file with a Zip archiver like 7zip. +3. Go in the folder build/netstandard2.0/ref/ and you will find netstandard.dll. +4. Place the netstandard.dll somewhere in your C: drive where you will be able to find it, and remember that location. You could place it in the BHoM installation directory (normally C:\ProgramData\BHoM\), but be aware that if you reinstall or update BHoM it will get deleted.

+
+

Note

+

If you downloaded netstandard.dll previously but you can't remember where you placed it, you can search for a copy of netstandard.dll in your disk. +⚠️ However, there could be multiple copies/versions of a netstandard.dll file on your drive. If you find multiple files called netstandard.dll, then it's better to re-download it from the link above to make sure you are using the right version. ⚠️

+

Search for netstandard.dll in Explorer from your C: drive:

+

Alt text

+

Once found, get its location by right-clicking on it and doing "Open location", then copy the location in Explorer. Take note of it.

+
+

Reference the assemblies in the C# Script component

+

To start coding let's create a "C# Script" component in Grasshopper where we will reference the required DLLs.

+
    +
  1. Drop a "C# Script" component in the canvas.
  2. +
  3. Right click it, do "Manage Assemblies". A window will pop up.
  4. +
  5. Click "Add". A File Explorer window will pop up.
  6. +
  7. Add a reference to the netstandard.dll file, found as explained above. Select it and do "Open". You will see that it appears in the Referenced Assemblies section.
  8. +
  9. Click "Add" again. Navigate to the BHoM assemblies directory (normally C:\ProgramData\BHoM\Assemblies). There you will find all BHoM DLLs. As a minimum, we will want to include BHoM.dll and BHoM_Engine.dll. We can add as many as we need, but don't add them all together. You will come back to add more in case the script complains that some are missing.
  10. +
+

Referenced assemblies

+

Start scripting!

+

Let's make an example where we want to create a BH.oM.Geometry.Point object in the script. To do so, we need to add another 2 references, Geometry_oM.dll and Dimensional_oM.dll. Let's do that as explained above. We will end up having the following:

+

Alt text

+

Next, let's open the script and write: +

BH.oM.Geometry.Point p = new BH.oM.Geometry.Point();
+p.X = 3;
+p.Y = 5;
+p.Z = 1;
+
+A = p;
+

+

You will have this:

+

Alt text

+

Press OK, and voila, a BHoM point is created! You can also check its values with the Explode component:

+

Alt text

+

Script with more complex objects

+

Do the same for any other BHoM object you may want to create. Using more complex objects will require to add more references, like explained in the previous section. For example, if we want to create a structural node with this point, we can do:

+
BH.oM.Geometry.Point p = new BH.oM.Geometry.Point();
+p.X = 3;
+p.Y = 5;
+p.Z = 1;
+
+BH.oM.Structure.Elements.Node node = new BH.oM.Structure.Elements.Node();
+node.Position = p;
+
+A = p;
+
+

However, if you press OK, you will be met with an error like:

+

Alt text

+

This simply means that you need to add references to Structure_oM.dll. If we add that and try again, the error will still not go away, but will be different:

+

Alt text

+

This is because the Structure_oM.dll itself depends on Analytical_oM.dll.
+By adding this last dependency the error will go away.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/Using Visual Studio Code/index.html b/Guides and Tutorials/Coding with BHoM/IDEs/Using Visual Studio Code/index.html new file mode 100644 index 000000000..7ab00cb95 --- /dev/null +++ b/Guides and Tutorials/Coding with BHoM/IDEs/Using Visual Studio Code/index.html @@ -0,0 +1,4444 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Using Visual Studio Code For BHoM Development - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Using Visual Studio Code For BHoM Development

+

Visual Studio Code as of June 2023 is a viable option for .Net and C# development on windows.

+

Required Software

+ +

Required Configuration

+

Attach to Process For Debugging

+

Add a folder to your tool's root directory titled .vscode +Add a file to .vscode folder titled launch.json +

Tool_Repo_Folder
+|
+|--- .vscode
+      |
+      |---launch.json
+

+

Attach to process of your choosing

+

within your launch.json +add the following

+
{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    //To attach to running process of your choosing
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Attach to Process",
+            "type": "clr", 
+            "request": "attach",
+            "processId": "${command:pickProcess}"
+        }
+      ],
+        "postDebugTask": "echo"
+}
+
+

Attach to running process of pre-defined name

+

within your launch.json +add the following

+
{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    //To attach to running process automatically
+    "version": "0.2.0",
+    "configurations": [
+         {
+             "name": "Attach to Revit",
+             "type": "coreclr", 
+             "request": "attach",
+             "processName": "Revit.exe",
+             "justMyCode": false
+         }
+    ],
+        "postDebugTask": "echo"
+}
+
+

Working As A Team

+

Not all members of the team will want to work in VS Code. +Please be considerate of this as you develop by adding the .vscode folder to your .gitignore

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/examplescript.png b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript.png new file mode 100644 index 000000000..28739d923 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_error01.png b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_error01.png new file mode 100644 index 000000000..8c1d358c0 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_error01.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_error02.png b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_error02.png new file mode 100644 index 000000000..4e63107c4 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_error02.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_output.png b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_output.png new file mode 100644 index 000000000..7fa5bb3db Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/examplescript_output.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/image-1.png b/Guides and Tutorials/Coding with BHoM/IDEs/image-1.png new file mode 100644 index 000000000..528206c86 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/image-1.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/image-2.png b/Guides and Tutorials/Coding with BHoM/IDEs/image-2.png new file mode 100644 index 000000000..42b9317d6 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/image-2.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/image-8.png b/Guides and Tutorials/Coding with BHoM/IDEs/image-8.png new file mode 100644 index 000000000..808af1a97 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/image-8.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/referenced_assemblies01.png b/Guides and Tutorials/Coding with BHoM/IDEs/referenced_assemblies01.png new file mode 100644 index 000000000..c19c2fdde Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/referenced_assemblies01.png differ diff --git a/Guides and Tutorials/Coding with BHoM/IDEs/referenced_assemblies02.png b/Guides and Tutorials/Coding with BHoM/IDEs/referenced_assemblies02.png new file mode 100644 index 000000000..44b7b8ca5 Binary files /dev/null and b/Guides and Tutorials/Coding with BHoM/IDEs/referenced_assemblies02.png differ diff --git a/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Configuring-Revit-objects-comparison-(RevitComparisonConfig)/index.html b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Configuring-Revit-objects-comparison-(RevitComparisonConfig)/index.html new file mode 100644 index 000000000..f5550c679 --- /dev/null +++ b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Configuring-Revit-objects-comparison-(RevitComparisonConfig)/index.html @@ -0,0 +1,4361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Configuring Revit objects comparison (RevitComparisonConfig) - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Configuring Revit objects comparison (RevitComparisonConfig)

+ +

As we've seen in the Diffing and Hash pages, we can customise how objects are compared to each other (either using Diffing or by comparing their Hashes) through the ComparisonConfig object.

+

In addition to the basic ComparisonConfig that we can use with any object, we also have a Revit-specific RevitComparisonConfig object that expands the available options.

+

Below is an example of how the RevitComparisonConfig looks in Grasshopper. Note that most of them are already covered by the ComparisonConfig object base wiki, while the Revit-specific options are only the first 4 (explained below).

+

image

+
+

Note for developers: Toolkit-specific ComparisonConfig objects

+

The "default" comparisonConfig object inherits from the BaseComparisonConfig abstract class, which defines all the "basic" options. This abstract class can be extended by the "Toolkit-specific" comparisonConfigs, so you can include additional options to deal with certain objects in your Toolkit, of which RevitComparisonConfig is an example.

+

In general, if you implement your own Toolkit-specific comparisonConfig object, you will need to implement the functions that deal with it, i.e. a toolkit-specific Diffing() method and a toolkit-specific HashString() method.

+

The RevitComparisonConfig is in fact used by the RevitDiffing() method, and, when hashing, by Revit's HashString() method. These two methods can be invoked manually, to deal with Revit Objects, or are automatically invoked by the IDiffing() method when the input objects are Revit objects.

+
+

ParametersExceptions

+

Allows to specify Revit Parameter names that should not be considered while Diffing or computing an object's Hash.

+

This supports * wildcard matching.

+

ParametersToConsider

+

The ParametersToConsider input allows you to add parameter names that should be considered while Diffing or computing an object's Hash.

+

If you add a parameter name in this field, only the value held in that parameter will be considered.

+

If the parameter name that you specified is not found on the object, then no parameter will be considered for that object.

+

This input supports * wildcard matching.

+

ParameterNumericTolerance

+

This works similarly to the PropertyNumericTolerance option, but it applies to Revit Parameters only. See that wiki section for more details on how to use it.

+

ParameterSignificantFigures

+

This works similarly to the PropertySignificantFigures option, but it applies to Revit Parameters only. See that wiki section for more details on how to use it.

+



+
+

For a description of all remaining options, see /Configuring-objects-comparison:-%60ComparisonConfig%60.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Configuring-objects-comparison-(ComparisonConfig)/index.html b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Configuring-objects-comparison-(ComparisonConfig)/index.html new file mode 100644 index 000000000..97033e673 --- /dev/null +++ b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Configuring-objects-comparison-(ComparisonConfig)/index.html @@ -0,0 +1,4959 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Configuring objects comparison (ComparisonConfig) - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

As seen in the Diffing and the Hash wiki pages, the real power of object comparison is given by the options that you have when performing it. For this reason, we expose options to customise these operations via the ComparisonConfig object.

+

Here is an example of the ComparisonConfig object seen from Grasshopper:

+

2021-12-09 16_07_51-Grasshopper - ComparisonConfig versioning check_

+

There are also "Toolkit-specific" ComparisonConfig objects that extend the available options when dealing with certain objects, for example Revit's RevitComparisonConfig gives further options when dealing with Revit objects. More details on it in its dedicated page.

+
+

Note for developers

+

The "default" comparisonConfig object inherits from the BaseComparisonConfig abstract class, which defines all the "basic" options. This abstract class can be extended by the "Toolkit-specific" comparisonConfigs, so you can include additional options to deal with certain objects in your Toolkit, of which RevitComparisonConfig is an example.
+If you implement your own Toolkit-specific comparisonConfig object, you will need to implement the functions that deal with it too, which are a toolkit-specific Diffing() method (example in Revit), a toolkit-specific HashString() method (example in Revit), and any number of ComparisonInclusion() methods that you might need (example in Revit). More details can be found in the diffing guide for developers.

+
+

Description of the ComparisonConfig options

+

Let's see the ComparisonConfig options in detail.

+

Many of the following examples use the Bar class as a reference object.

+ +

PropertyExceptions

+

You can specify one or more names of properties that you want to ignore (not consider, take as exceptions) when comparing objects. +This allows to ignore properties and also sub-properties (i.e., properties of properties) of any object. +This also supports * wildcard within property names, so you can match multiple properties.
+You can specify either the simple name of the property (e.g. StartNode), or the FullName of the property (e.g. BH.oM.Structure.Elements.Bar.StartNode) if you want to be more precise and avoid confusion in case you have properties/sub-properties with the same name.

+

To clarify the above, here are examples using the Bar class as a reference:

+

Property name VS Property Full Name - examples

+
    +
  • Specifying the property name StartNode would ignore the StartNode property. It follows that any sub-property of StartNode will also be ignored.
  • +
  • Specifying the property Full Name BH.oM.Structure.Element.Bar.StartNode would achieve the same result, but it is safer than using only the simple name StartNode (and may as well save computation time, like in the case of PropertiesToConsider when Hashing).
  • +
+

To explain why using the property Full Name is safer, consider the example where you are Diffing a mix of objects which include both Bars and also GraphLinks, both of which own a StartNode property. If you input StartNode in the PropertyExceptions, you must be aware that both properties BH.oM.Structure.Elements.Bar.StartNode and BH.oM.Data.Collections.GraphLink.StartNode will be treated as exceptions, hence ignored. Specifying the property full name is safer.

+

Sub-properties examples

+ +

Wildcard examples

+

You can specify * wildcards within property names, so you can match multiple properties with a single text.

+
    +
  • Specifying BH.oM.Structure.Elements.Bar.*.Position.Y would match:
  • +
  • BH.oM.Structure.Elements.Bar.StartNode.Position.Y
  • +
  • BH.oM.Structure.Elements.Bar.EndNode.Position.Y
  • +
+

so if this is specified in the PropertyExceptions, those 2 properties will be ignored.

+
    +
  • Specifying BH.oM.Structure.Elements.Bar.*.Y would match:
  • +
  • BH.oM.Structure.Elements.Bar.StartNode.Position.Y
  • +
  • BH.oM.Structure.Elements.Bar.EndNode.Position.Y
  • +
  • BH.oM.Structure.Elements.Bar.StartNode.Orientation.Y
  • +
  • BH.oM.Structure.Elements.Bar.EndNode.Orientation.Y
  • +
+

so if this is specified in the PropertyExceptions, those 4 properties will be ignored.

+
    +
  • +

    Again, you can specify only the name instead of the Full Name to obtain the same result, i.e. *.Position.Y would achieve the same result as BH.oM.Structure.Elements.Bar.*.Position.Y when the input objects are only Bars, but you incur in the same risks illustrated above if your input objects are of different types (see property name VS property Full Name).

    +
  • +
  • +

    You can add as many * wildcards as you wish, which is especially handy when you have input objects of different types. Specifying BH.oM.Structure.*.Start*.*Y with both Bars and BarReleases input objects would match all of the following properties:

    +
  • +
  • BH.oM.Structure.Elements.Bar.StartNode.Position.Y
  • +
  • BH.oM.Structure.Elements.Bar.StartNode.Orientation.Y
  • +
  • BH.oM.Structure.Elements.Bar.StartNode.Offset.Start.Y
  • +
  • BH.oM.Structure.Constraints.BarRelease.StartRelease.TranslationalStiffnessY
  • +
  • BH.oM.Structure.Constraints.BarRelease.StartRelease.RotationalStiffnessY
  • +
  • BH.oM.Structure.Constraints.BarRelease.StartRelease.TranslationY
  • +
  • BH.oM.Structure.Constraints.BarRelease.StartRelease.RotationY
  • +
+

so if this is specified in the PropertyExceptions, and both Bars and BarReleases are in the input objects, all those 7 properties will be ignored.
+If instead you only had Bars in the input objects, the BH.oM.Structure.*.Start*.*Y would only match the first 3 properties in the list above.

+

PropertiesToConsider

+

The PropertiesToConsider input allows you to add property names that should be considered in the comparison.

+

If you add a property name in this field, only the value held in that property will be considered.

+

If the property name that you specified is not found on the object, then no properties will be considered. Therefore, make sure you input property names that exist on the object.

+

Like for the PropertyExceptions option, you can specify the property names as just the Name (e.g. StartNode), as a Full Name (e.g. BH.oM.Structure.Elements.Bar.StartNode) and/or using wildcards (e.g. BH.oM.Structure.Elements.*.StartNode) to get different matching results. See the section on PropertyExceptions for more details on Full Names and using wildcards.

+
+

Note: Hash performance when using PropertiesToConsider

+

Using PropertiesToConsider can be a resource-intensive operation when calculating an object's Hash (Diffing instead is only slightly affected). To speed up the Hash computation: +- use only property Full Names as an input to PropertiesToConsider; +- do not use Wildcards in PropertiesToConsider; +- limit the amount of property names in PropertiesToConsider.

+

Technical explanation in the details below.

+
+

The Hash of an object is calculated by recursively navigating all properties of the object and taking their value. If you specify some PropertiesToConsider, the property value is only considered if its name matches a property name in there. Then, the recursion continues, and if the current property has some sub-property, the algorithm checks the sub-property, and so on. When checking a certain property, the algorithm doesn't know the names of all its sub-properties until it gets there.

+

If the property names include Wildcards or are not specified as Full Names, there can be situations where some nested sub-property needs to be considered, but then its parent's siblings must be ignored. When computing the Hash(), we are traversing the property tree of the object, but we do not know all the properties during the traversal. For example, say that your input object is a Bar, and you want to consider exclusively properties that match *.Name. The situation is:

+

image

+

One way of solving this could be to "consider" all the properties of the object while doing the Hash, and, at the end, cull away those that do not match any PropertiesToConsider. This basically is like saying that we build our knowledge of the object while computing its Hash. However, this can be wasteful for two reasons: +- speed: many other operations may be done to the object values being considered when computing the Hash (e.g. numerical approximations); +- space: we would need to store in RAM many values that we may never use.

+

For this reason, we instead build the knowledge of the property tree before computing the hash; in other words, we traverse the entire object once and look at the property names, and get the "consequent" PropertiesToConsider, i.e. all the properties of the object that match your wildcard or partial property name, translated to their Full Name form. By using Full Names, it the becomes easy for the Hash algorithm to consider or not a property: just check if the property full name matches any of the PropertiesToConsider.

+

The cost of this can be cut by specifying Full Names instead of just the name (i.e. BH.oM.Structure.Elements.Bar.StartNode instead of StartNode) and avoiding wildcards * when using PropertiesToConsider.

+
+
+

CustomDataKeysExceptions

+

This works similarly to PropertyExceptions, but is used only for BHoMObjects CustomData dictionary keys.

+

Setting a key name in CustomDataKeysExceptions means that if that key is found on in the CustomData dictionary of an object, it will not be considered.

+

This option does not support wildcard, unlike PropertyExceptions.

+

CustomDataKeysToConsider

+

Setting a key name in CustomDataKeysToConsider means that only that dictionary key will be considered amongst any BHoMObject's CustomData. If no matching CustomData key is found on the object, no CustomData entry will be considered.

+

This option does not support wildcard.

+

TypeExceptions

+

You can input any Type here. Any object or property of corresponding types will not be considered.

+

NamespaceExceptions

+

You can input the name of any namespace here. An example of a namespace is BH.oM.Structure.Elements.

+

Any object or property that belong to the corresponding namespace will not be considered.

+

This option does not support wildcard.

+

MaxNesting

+

This option limits the depth of property discovery when computing Diffing or an object's Hash.

+

Properties whose Nesting Level is equal to or larger than MaxNesting will not be considered.

+
+

Property Nesting Level definition

+

The nesting level of a property defines how deep we are in the object property tree.
+For example: +- a Bar's StartNode property is at Nesting Level 1 (it is also called a "top-level" property of the object) +- a Bar's StartNode.Position property is at Nesting Level 2, because Position is a sub-property of StartNode.

+
+

Top-level properties are at level 1. Setting MaxNesting to 1 will make the Hash or Diffing consider only top-level properties. Setting MaxNesting to 0 will disregard any object property (only the class name will end up in the Hash, and Diffing will not find any differences).

+

This option is better used as a safety measure to avoid excessive computation time when diffing or computing the hash for objects that may occasionally have one or more deeply nested properties of which we do not care about.

+

MaxPropertyDifferences

+

When Diffing, this indicates the maximum number of Property Differences that will be collected and returned. This setting does not affect the Hash calculation (in fact, this option should be moved in DiffingConfig instead).

+

You can not control what properties are returned and what remain excluded due to this numeric limit. Hence, this option is better used as a safety measure to avoid excessive computation time when: +- we care about finding different objects, but do not care about what properties did change between them, although a better and faster option for this would be to use DiffingConfig.EnablePropertyDiffing set to false; +- we are okay with finding only the first n differences between objects, whatever those may be.

+

NumericTolerance

+

This option sets the Numeric tolerance applied when considering any numerical property of objects. +For example, a Bar's StartNode.Position.X property is a numerical property.

+

When a numerical property is encountered, the function BH.Engine.Base.RoundWithTolerance() is applied to its value, which becomes approximated with the given NumericTolerance.

+

Therefore, when Hashing, the property's approximate value will be recorded in the Hash. When Diffing, the property approximate value will be used for the comparison.

+

If both NumericTolerance and SignificantFigures are provided in the ComparisonConfig, both approximations are executed, and the largest approximation among all (the least precise number) is registered.

+
+

RoundWithTolerance() details

+

The function BH.Engine.Base.RoundWithTolerance() will approximate the input value with the given tolerance, which is done by rounding (to floor) to the nearest tolerance multiplier.

+

Some examples of RoundWithTolerance() are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input numberInput ToleranceResult (approximated number)
12200
1212120
1.23451.11.1
0.0140.010.01
0.0140.020
+
+

PropertyNumericTolerance

+

This option applies a given Numeric Tolerance to a specific property, therefore considering its value approximated using the given tolerance.

+

In order to use it, you have to create and input in PropertyNumericTolerance one or more NamedNumericTolerance objects, where you set: +- the Name of the property you want to target; this supports * wildcard usage; +- the Tolerance that you want to apply to the given property.

+

The approximation will work exactly as per the NumericTolerance option, only it will target exclusively the properties with the name specified via the NamedNumericTolerance objects.

+

If a match is found, this takes precedence over the NumericTolerance option.
+If conflicting values/multiple matches are found among the ComparisonConfig's numerical precision options, the largest approximation among all (least precise number) is registered.

+

The Name field supports wildcard usage. Some examples: +- BH.oM.Geometry.Vector: applies the corresponding tolerance to all numerical properties of Vectors, i.e. X, Y, Z +- BH.oM.Structure.Elements.*.Position: applies the corresponding tolerance to all numerical properties of properties named Position under any Structural Element, e.g. Bar.Position.X, Bar.Position.Y, Bar.Position.Z and at the same time also Node.Position.X, Node.Position.Y, Node.Position.Z.

+

SignificantFigures

+

This option sets the Significant Figures considered for any numerical property of objects. +For example, a Bar's StartNode.Position.X property is a numerical property.

+

When a numerical property is encountered, the function BH.Engine.Base.RoundToSignificantFigures() is applied to its value, which becomes approximated with the given SignificantFigures.

+

Therefore, when Hashing, the property's approximate value will be recorded in the Hash. When Diffing, the property approximate value will be used for the comparison.

+

If both SignificantFigures and NumericTolerance are provided in the ComparisonConfig, both approximations are executed, and the largest approximation among all (the least precise number) is registered.

+
+

RoundToSignificantFigures() details

+

The function BH.Engine.Base.RoundToSignificantFigures() will approximate the input value with the given Significant Figures. Some examples:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input numberInput Significant FiguresResult (approximated number)
1050.6711000
1050.6721100
1050.6731050
1050.6741051
1050.6751050.7
123456.1237123456.1
123456.1231100000
0.000000000000000000012345678951.2346E-20
0.0000000000000000000123456789991.23456789E-20
+
+

PropertySignificantFigures

+

This option applies the approximation with given Significant Figures to a specific property.

+

In order to use it, you have to create and input in PropertyNumericTolerance one or more NamedSignificantFigures objects, where you set: +- the Name of the property you want to target; this supports * wildcard usage; +- the SignificantFigures that you want to consider when evaluating the given property.

+

The approximation will work exactly as per the SignificantFigures option, only it will target exclusively the properties with the name specified via the NamedSignificantFigures objects.

+

If a match is found, this takes precedence over the SignificantFigures option.
+If conflicting values/multiple matches are found among the ComparisonConfig's numerical precision options, the largest approximation among all (least precise number) is registered.

+

The Name field supports wildcard usage. Some examples: +- BH.oM.Geometry.Vector: applies the corresponding tolerance to all numerical properties of Vectors, i.e. X, Y, Z +- BH.oM.Structure.Elements.*.Position: applies the corresponding tolerance to all numerical properties of properties named Position under any Structural Element, e.g. Bar.Position.X, Bar.Position.Y, Bar.Position.Z and at the same time also Node.Position.X, Node.Position.Y, Node.Position.Z.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Diffing-\342\200\223-tracking-changes-in-your-BHoM-objects/index.html" "b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Diffing-\342\200\223-tracking-changes-in-your-BHoM-objects/index.html" new file mode 100644 index 000000000..157afb7e0 --- /dev/null +++ "b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Diffing-\342\200\223-tracking-changes-in-your-BHoM-objects/index.html" @@ -0,0 +1,4580 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Diffing: tracking changes between objects - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Diffing: tracking changes between objects

+

Diffing is the process of determining what changed between two sets of objects.

+

Typically, the two sets of objects are two versions of the same thing (of a pulled Revit model, of a Structural Model that we want to Push to an Adapter, etc), in which case Diffing can effectively be used as a Version Control tool.

+

🤖 Developers: check out also the Diffing and Hash: Guide for developers.

+

image

+

The Diffing_Engine gives many ways to perform diffing on sets of objects. Let's see them.

+

IDiffing method

+

The most versatile method for diffing is the BH.Engine.Diffing.Compute.Diffing() method, also called IDiffing. Ideally, you should always use this Diffing method, although other alternatives exist for specific cases (see Other diffing methods below). A detailed technical explanation of the IDiffing can be found in the guide for developers.

+

This method can be found in any UI by simply looking for diffing. See the below for an example file:

+
+

Diffing main method

+
+
+
+

Example file (right click -> download): DiffingExample-00-RevitDiffing.zip

+

image +image

+
+
+

Example file (right click -> download): DiffingInExcel.xlsx

+

image

+
+
+
+
+

The method takes three inputs:

+
    +
  • pastObject: objects belonging to a past version, a version that precedes the followingObjects's version.
  • +
  • followingObjects: objects belonging to a following version, a version that was created after the pastObject's version.
  • +
  • diffingConfig: configurations for the diffing, where you can set your ComparisonConfig object, see below.
  • +
+

The output of every diffing method is always a diff object, which we will describe in a section below.

+
+

How diffing works: identifiers

+

The IDiffing, like all diffing methods, relies on an identifier assigned to each object, which can be used to match objects, so it knows which to compare to which.

+

The identifer is generally a unique "signature" assigned to each object, and this signature is assumed to remain always the same even if the object is modified.

+

The identifier is typically stored on objects after they have been Pulled from an Adapter. This means that the IDiffing works best with objects pulled from a BHoM Adapter that stores the object Id on the object (most of them do).

+

In case no Identifier can be found on the objects, the IDiffing attempts to use alternative methods e.g. compare one-by-one the objects; it will give you a note if this happens.

+

(Technical sidenote: the identifier object is of a type called IPersistentAdapterId, searched in the object's Fragments. More on this in the diffing guide for developers.)

+
+

The Diffing output: the Diff object

+

The output of any Diffing method is an object of type Diff. The diff output can be Exploded to reveal all the available outputs:

+
+

the Diff object

+
+
+
+

Example file (right click -> download): DiffingExample-00-RevitDiffing.zip

+

image

+
+
+

Example file (right click -> download): DiffingInExcel.xlsx

+

image

+
+
+
+
+
    +
  • AddedObjects: objects present in the second set that are not present in the first set.
  • +
  • RemovedObjects: objects not present in the second set that were present in the first set.
  • +
  • ModifiedObjects: objects that are recognised as present both in the first set and the second set, but that have some property that is different. The rules that were used to recognise modification are in the DiffingConfig.ComparisonConfig.
  • +
  • UnchangedObjects: objects that are recognised as the same in the first and second set.
  • +
  • ModifiedObjectsDifferences: all the differences found between the two input sets of objects.
  • +
  • DiffingConfig: the specific instance of DiffingConfig that was used to calculate this Diff. Useful in scenarios where a Diff is stored and later inspected.
  • +
+

The ModifiedObjectDifferences output contains a List of ObjectDifferences objects, one for each modified object, that contains information about the modified objects. These can be further Exploded:

+
+

The Diff object's properties

+
+
+
+

Example file (right click -> download): DiffingExample-00-RevitDiffing.zip

+

image

+
+
+

Example file (right click -> download): DiffingInExcel.xlsx

+

image

+
+
+
+
+
    +
  • PastObject: the object in the pastObjs set that was identified as modified (i.e., a different version of the same object was found in the followingObjs set).
  • +
  • FollowingObject: the object in the followingObjs set that was identified as modified (i.e., a different version of the same object was found in the pastObjs set).
  • +
  • Differences: all the differences found between the two versions of the modified object. This is a List of PropertyDifference objects, one for each difference found on the modified object.
  • +
+

Finally, exploding the Differences object, we find:

+
+

The Differences property

+
+
+
+

Example file (right click -> download): DiffingExample-00-RevitDiffing.zip

+

image

+
+
+

Example file (right click -> download): DiffingInExcel.xlsx

+

(Sorry, missing a more accurate screenshot here -- just keep exploding as in the grasshopper example) +image

+
+
+
+
+
    +
  • DisplayName: name given to the difference found. This is generally the PropertyName (name of the property that changed), but it can also indicate other things. For example, if a ComparisonInclusion() extension method is defined for some of the input objects (like it happens for Revit's RevitParameters), then the DisplayName may also contain some specific naming useful to identify the difference (in the case of RevitParameter, this is the name of the RevitParameter that changed in the modified object).
    +An example of a DisplayName could be StartNode.Position.X (given a modified object of type BH.oM.Structure.Elements.Bar).
  • +
  • PastValue: the modified value in the PastObject.
  • +
  • FollowingValue: the modified value in the FollowingObject.
  • +
  • FullName: this is the modified property Full Name. An object difference can always be linked to a precise object property that is different; this is given in the Full Name form, which includes the namespace. An example of this could be BH.oM.Structure.Elements.Bar.StartNode.Position.X. Note that this FullName can be significantly different from DisplayName (as happens for RevitParameters, where the Full Name will be something like e.g. BH.oM.Adapters.Revit.Parameters[3].RevitParameter.Value).
  • +
+

Options for the diffing: DiffingConfig (and ComparisonConfig)

+

The DiffingConfig object can be attached to any Diffing method and allows you to specify options for the Diffing comparison.

+

New Project (12) +The Diffing config has the following inputs:

+
    +
  • ComparisonConfig allows you to specify all the object comparison options; it has many settings, please see its dedicated page.
  • +
  • EnablePropertyDiffing: optional, defaults to true. If disabled, Diffing does not checks all the property-level differences, running much faster but potentially ignoring important changes.
  • +
  • IncludedUnchangedObjects: optional, defaults to true. When diffing large sets of objects, you may want to not include the objects that did not change in the diffing output, to save RAM.
  • +
  • AllowDuplicateIds: optional, defaults to false. The diffing generally uses identifiers to track "who is who" and decide which objects to compare; in such operations, duplicates should never be allowed, but there could be edge cases where it is useful to keep them.
  • +
+

Other Diffing methods

+

In addition to the main Diffing method IDiffing(), there are several other methods that can be used to perform Diffing. These are a bit more advanced and should be used only for specific cases. The additional diffing methods can be found in the Compute folder of Diffing_Engine.

+

Other than these, Toolkit-specific diffing methods exist to deal with the subtleties of comparing Objects defined in a Toolkit. Users do not generally need to know about these, as Toolkit-specific diffing methods will be automatically called for you if needed by the generic IDiffing method. Just for reference, a Toolkit-specific Diffing method is RevitDiffing().

+

DiffWithFragmentId() and DiffWithCustomDataKeyId()

+

These two methods are "ID-based" diffing methods. They simply retrieve an Identifier associated to the input objects, and use it to match objects from the pastObjs set to objects in the followingObjs set, deciding who should be compared to who.

+
    +
  • The DiffWithFragmentId() retrieves object identifiers from the objects' Fragments. You can specify which Fragment you want to get the ID from, and which property of the fragment is the ID.
  • +
  • The DiffWithCustomDataKeyId() retrieves object identifiers from the objects' CustomData dictionary. You can specify which dictionary Key you want to get the ID from.
  • +
+

Both method then call the DiffWithCustomIds() to perform the comparison with the extracted Ids, see below.

+

DiffWithCustomIds()

+

The DiffWithCustomIds() method allows you to provide:

+
    +
  • Two input objects sets that you want to compare, pastObjs and followingObjs;
  • +
  • Two input identifiers sets, pastObjsIds and followingObjsIds, with the Ids associated to the pastObjs and followingObjs.
  • +
+

You can specify some null Ids in the pastObjsIds and followingObjsIds; however these two lists must have the same number of elements as pastObjs and followingObjs, respectively.

+

The IDs are then used to match the objects from the pastObjs set to objects in the followingObjs set, to decide who should be compared to who:

+
    +
  • If an object in the pastObjs does not have a corresponding object in the followingObjs set, it means that it has been deleted in the following version, so it is identified as "Removed" (old).
  • +
  • If an object in the followingObjs does not have a corresponding object in the pastObjs set, it means that it has been deleted in the past version, so it is identified as "Added" (new).
  • +
  • If an object in the pastObjs matches by ID an object in the followingObjs, then it is identified as "Modified" (it changed between the two versions). This means that the two objects will be compared and all their differences will be found. This is done by invoking the ObjectDifferences() method, that is explained in detail here.
  • +
+

DiffOneByOne()

+

The DiffOneByOne() method simply takes two input lists, pastObjs and followingObjects, and these have the objects in the same identical order. It then simply compares each object one-by-one. If matched objects are equal, they are "Unchanged", otherwise, they are "Modified" and their property difference is returned.

+

For this reason, this method is not able to discover "Added" (new) or "Removed" (old) objects.

+

DiffWithHash()

+

The DiffWithHash() method simply does a Venn Diagram of the input objects' Hashes:

+

image

+

The Venn Diagram is computed by means of a HashComparer, which simply means that the Hash of all input objects gets computed.

+

If objects with the same hash are found they are identified as "Unchanged"; otherwise, objects are either "Added" (new) or "Removed" (old) depending if their hash exists exclusively in following or past set. For this reason, this method is not able to discover "Modified" objects.

+

The Hash is leveraged by this method so you are able to customise how the diffing behaves by specifying a ComparisonConfig options in the DiffingConfig.

+

DiffRevisions

+

This method was designed for the AECDeltas workflow and is currently not widely used.

+

It essentially expects the input objects to be wrapped into a Revision object, which is useful to attach additional Versioning properties to them. +The Revisions can then be provided as an input to DiffRevisions(), and the logic works very similarly to the other diffing methods seen above.

+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Hash-\342\200\223-an-object's-identity/index.html" "b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Hash-\342\200\223-an-object's-identity/index.html" new file mode 100644 index 000000000..8b5089b4b --- /dev/null +++ "b/Guides and Tutorials/Visual Programming with BHoM/Diffing and Hashing/Hash-\342\200\223-an-object's-identity/index.html" @@ -0,0 +1,4348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Hash – an object's identity - BHoM documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Hash – an object's identity

+ +

Hash definition

+

A Hash, sometimes called also hash code, is the "unique signature" or "identity" of an object.

+

The hash is generally a string (a text) containing alphanumeric characters. It is composed by applying a Hash algorithm to an object, which parses the input object, all its properties, and the values assigned to those properties. The returned hash is a "combination of all the variables" present in the object. It follows that its most important feature is that the hash remains the same as long as the object remains the same (under certain criteria, which can be customised).

+

The Hash for objects can be used in many different kinds of comparisons, or for any case where a unique identification of an object is needed. Examples include: +- you can compute hash for objects to quickly and safely compare objects with each other, so you can determine unique objects (i.e., what objects are duplicates or not) +- you can compare an object's hash at different points in time. You can store the hash of an object in a certain moment; then, some time later, you can check if the object changed (i.e., even a slight variation of one of its properties) by checking if its hash changed.

+

BHoM's Hash() method

+

BHoM exposes a Hash() method to calculate the Hash for any BHoM object (any object implementing the IObject interface).

+

This method is defined in the base BHoM_Engine: BH.Engine.Base.Query.Hash(). Here is an example of how the method can be used in Grasshopper:

+

image

+

The method returns a string, a textual Hash code that uniquely represents the input object.

+

This method's most parameters are: +- the IObject you want to get the Hash for; +- comparisonConfig configurations on how the Hash is calculated (see the dedicated section); +- hashFromFragment: if instead of computing the Hash of the object, you want to retrieve a Hash that was previously stored in the object's Fragments.
+ In order to set the HashFragment on a BHoMObject's Fragment, you can use the SetHashFrament() method: image

+

Hash ComparisonConfig: options to compute the Hash

+

The real potential of the Hash algorithm is given by its customisation options, which we call ComparisonConfig (comparison configurations).

+

For example, you may want to configure the Hash algorithm so it only considers numerical properties that changed within a certain tolerance. This way, you can determine if an object changed by looking at changes in the Hash, and you will be alerted only if the change was a numerical change greater than the given tolerance.

+

For this reasons, we expose many configurations in a ComparisonConfig object:

+

image

+

See the page dedicated to ComparisonConfig for details on it.

+

Note that some ComparisonConfig options may slow down the computation of the Hash, which becomes particularly noticeable when hashing large sets of objects. An option that may have particular negative impact when computing the Hash is PropertiesToConsider, as explained here.

+



+
+

Note for developers: customising an object's Hash

+

If you want a specific object to be Hashed in a particular way, you can implement a specific HashString() method for that object in your Toolkit.

+

Here is an example for Revit's RevitParameter object. The HashString() method will get invoked when computing the Hash().

+

More info in the Diffing and Hash: guide for developers wiki page.

+
+ +
+
+ + + Last update: + November 23, 2023 + + + +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/_logos/favicon.ico b/_logos/favicon.ico new file mode 100644 index 000000000..d709b31b1 Binary files /dev/null and b/_logos/favicon.ico differ diff --git a/_logos/logo.png b/_logos/logo.png new file mode 100644 index 000000000..166dcd90f Binary files /dev/null and b/_logos/logo.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.6c14ae12.min.js b/assets/javascripts/bundle.6c14ae12.min.js new file mode 100644 index 000000000..bbff77681 --- /dev/null +++ b/assets/javascripts/bundle.6c14ae12.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var wi=Object.create;var ur=Object.defineProperty;var Si=Object.getOwnPropertyDescriptor;var Ti=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Oi=Object.getPrototypeOf,dr=Object.prototype.hasOwnProperty,Zr=Object.prototype.propertyIsEnumerable;var Xr=(e,t,r)=>t in e?ur(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,R=(e,t)=>{for(var r in t||(t={}))dr.call(t,r)&&Xr(e,r,t[r]);if(kt)for(var r of kt(t))Zr.call(t,r)&&Xr(e,r,t[r]);return e};var eo=(e,t)=>{var r={};for(var o in e)dr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&kt)for(var o of kt(e))t.indexOf(o)<0&&Zr.call(e,o)&&(r[o]=e[o]);return r};var hr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Mi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Ti(t))!dr.call(e,n)&&n!==r&&ur(e,n,{get:()=>t[n],enumerable:!(o=Si(t,n))||o.enumerable});return e};var Ht=(e,t,r)=>(r=e!=null?wi(Oi(e)):{},Mi(t||!e||!e.__esModule?ur(r,"default",{value:e,enumerable:!0}):r,e));var ro=hr((br,to)=>{(function(e,t){typeof br=="object"&&typeof to!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(br,function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(C){return!!(C&&C!==document&&C.nodeName!=="HTML"&&C.nodeName!=="BODY"&&"classList"in C&&"contains"in C.classList)}function c(C){var it=C.type,Ue=C.tagName;return!!(Ue==="INPUT"&&s[it]&&!C.readOnly||Ue==="TEXTAREA"&&!C.readOnly||C.isContentEditable)}function p(C){C.classList.contains("focus-visible")||(C.classList.add("focus-visible"),C.setAttribute("data-focus-visible-added",""))}function l(C){C.hasAttribute("data-focus-visible-added")&&(C.classList.remove("focus-visible"),C.removeAttribute("data-focus-visible-added"))}function f(C){C.metaKey||C.altKey||C.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(C){o=!1}function d(C){a(C.target)&&(o||c(C.target))&&p(C.target)}function v(C){a(C.target)&&(C.target.classList.contains("focus-visible")||C.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(C.target))}function b(C){document.visibilityState==="hidden"&&(n&&(o=!0),z())}function z(){document.addEventListener("mousemove",G),document.addEventListener("mousedown",G),document.addEventListener("mouseup",G),document.addEventListener("pointermove",G),document.addEventListener("pointerdown",G),document.addEventListener("pointerup",G),document.addEventListener("touchmove",G),document.addEventListener("touchstart",G),document.addEventListener("touchend",G)}function K(){document.removeEventListener("mousemove",G),document.removeEventListener("mousedown",G),document.removeEventListener("mouseup",G),document.removeEventListener("pointermove",G),document.removeEventListener("pointerdown",G),document.removeEventListener("pointerup",G),document.removeEventListener("touchmove",G),document.removeEventListener("touchstart",G),document.removeEventListener("touchend",G)}function G(C){C.target.nodeName&&C.target.nodeName.toLowerCase()==="html"||(o=!1,K())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",b,!0),z(),r.addEventListener("focus",d,!0),r.addEventListener("blur",v,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Vr=hr((Ot,Dr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Ot=="object"&&typeof Dr=="object"?Dr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Ot=="object"?Ot.ClipboardJS=r():t.ClipboardJS=r()})(Ot,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ei}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(W){try{return document.execCommand(W)}catch(O){return!1}}var d=function(O){var S=f()(O);return u("cut"),S},v=d;function b(W){var O=document.documentElement.getAttribute("dir")==="rtl",S=document.createElement("textarea");S.style.fontSize="12pt",S.style.border="0",S.style.padding="0",S.style.margin="0",S.style.position="absolute",S.style[O?"right":"left"]="-9999px";var $=window.pageYOffset||document.documentElement.scrollTop;return S.style.top="".concat($,"px"),S.setAttribute("readonly",""),S.value=W,S}var z=function(O,S){var $=b(O);S.container.appendChild($);var F=f()($);return u("copy"),$.remove(),F},K=function(O){var S=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},$="";return typeof O=="string"?$=z(O,S):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?$=z(O.value,S):($=f()(O),u("copy")),$},G=K;function C(W){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(S){return typeof S}:C=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},C(W)}var it=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},S=O.action,$=S===void 0?"copy":S,F=O.container,Q=O.target,_e=O.text;if($!=="copy"&&$!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Q!==void 0)if(Q&&C(Q)==="object"&&Q.nodeType===1){if($==="copy"&&Q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if($==="cut"&&(Q.hasAttribute("readonly")||Q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(_e)return G(_e,{container:F});if(Q)return $==="cut"?v(Q):G(Q,{container:F})},Ue=it;function Pe(W){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Pe=function(S){return typeof S}:Pe=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},Pe(W)}function ui(W,O){if(!(W instanceof O))throw new TypeError("Cannot call a class as a function")}function Jr(W,O){for(var S=0;S0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Pe(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var Q=this;this.listener=p()(F,"click",function(_e){return Q.onClick(_e)})}},{key:"onClick",value:function(F){var Q=F.delegateTarget||F.currentTarget,_e=this.action(Q)||"copy",Ct=Ue({action:_e,container:this.container,target:this.target(Q),text:this.text(Q)});this.emit(Ct?"success":"error",{action:_e,text:Ct,trigger:Q,clearSelection:function(){Q&&Q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return fr("action",F)}},{key:"defaultTarget",value:function(F){var Q=fr("target",F);if(Q)return document.querySelector(Q)}},{key:"defaultText",value:function(F){return fr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var Q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return G(F,Q)}},{key:"cut",value:function(F){return v(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Q=typeof F=="string"?[F]:F,_e=!!document.queryCommandSupported;return Q.forEach(function(Ct){_e=_e&&!!document.queryCommandSupported(Ct)}),_e}}]),S}(a()),Ei=yi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s},438:function(o,n,i){var s=i(828);function a(l,f,u,d,v){var b=p.apply(this,arguments);return l.addEventListener(u,b,v),{destroy:function(){l.removeEventListener(u,b,v)}}}function c(l,f,u,d,v){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(b){return a(b,f,u,d,v)}))}function p(l,f,u,d){return function(v){v.delegateTarget=s(v.target,f),v.delegateTarget&&d.call(l,v)}}o.exports=c},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(o,n,i){var s=i(879),a=i(438);function c(u,d,v){if(!u&&!d&&!v)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(v))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,v);if(s.nodeList(u))return l(u,d,v);if(s.string(u))return f(u,d,v);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,v){return u.addEventListener(d,v),{destroy:function(){u.removeEventListener(d,v)}}}function l(u,d,v){return Array.prototype.forEach.call(u,function(b){b.addEventListener(d,v)}),{destroy:function(){Array.prototype.forEach.call(u,function(b){b.removeEventListener(d,v)})}}}function f(u,d,v){return a(document.body,u,d,v)}o.exports=c},817:function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Ha=/["'&<>]/;Nn.exports=$a;function $a(e){var t=""+e,r=Ha.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||a(u,d)})})}function a(u,d){try{c(o[u](d))}catch(v){f(i[0][3],v)}}function c(u){u.value instanceof Ze?Promise.resolve(u.value.v).then(p,l):f(i[0][2],u)}function p(u){a("next",u)}function l(u){a("throw",u)}function f(u,d){u(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function io(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof we=="function"?we(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function k(e){return typeof e=="function"}function at(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Rt=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=we(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(b){t={error:b}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(k(l))try{l()}catch(b){i=b instanceof Rt?b.errors:[b]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=we(f),d=u.next();!d.done;d=u.next()){var v=d.value;try{ao(v)}catch(b){i=i!=null?i:[],b instanceof Rt?i=D(D([],N(i)),N(b.errors)):i.push(b)}}}catch(b){o={error:b}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Rt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ao(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var gr=Ie.EMPTY;function Pt(e){return e instanceof Ie||e&&"closed"in e&&k(e.remove)&&k(e.add)&&k(e.unsubscribe)}function ao(e){k(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?gr:(this.currentObservers=null,a.push(r),new Ie(function(){o.currentObservers=null,De(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new P;return r.source=this,r},t.create=function(r,o){return new ho(r,o)},t}(P);var ho=function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:gr},t}(x);var yt={now:function(){return(yt.delegate||Date).now()},delegate:void 0};var Et=function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=yt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=lt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(lt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(jt);var go=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(Wt);var Oe=new go(vo);var L=new P(function(e){return e.complete()});function Nt(e){return e&&k(e.schedule)}function Or(e){return e[e.length-1]}function Qe(e){return k(Or(e))?e.pop():void 0}function Me(e){return Nt(Or(e))?e.pop():void 0}function Ut(e,t){return typeof Or(e)=="number"?e.pop():t}var mt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Dt(e){return k(e==null?void 0:e.then)}function Vt(e){return k(e[pt])}function zt(e){return Symbol.asyncIterator&&k(e==null?void 0:e[Symbol.asyncIterator])}function qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Pi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Kt=Pi();function Qt(e){return k(e==null?void 0:e[Kt])}function Yt(e){return no(this,arguments,function(){var r,o,n,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,Ze(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,Ze(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,Ze(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return k(e==null?void 0:e.getReader)}function I(e){if(e instanceof P)return e;if(e!=null){if(Vt(e))return Ii(e);if(mt(e))return Fi(e);if(Dt(e))return ji(e);if(zt(e))return xo(e);if(Qt(e))return Wi(e);if(Bt(e))return Ni(e)}throw qt(e)}function Ii(e){return new P(function(t){var r=e[pt]();if(k(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Fi(e){return new P(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?M(function(n,i){return e(n,i,o)}):ue,xe(1),r?He(t):Io(function(){return new Jt}))}}function Fo(){for(var e=[],t=0;t=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,d=0,v=!1,b=!1,z=function(){f==null||f.unsubscribe(),f=void 0},K=function(){z(),l=u=void 0,v=b=!1},G=function(){var C=l;K(),C==null||C.unsubscribe()};return g(function(C,it){d++,!b&&!v&&z();var Ue=u=u!=null?u:r();it.add(function(){d--,d===0&&!b&&!v&&(f=Hr(G,c))}),Ue.subscribe(it),!l&&d>0&&(l=new tt({next:function(Pe){return Ue.next(Pe)},error:function(Pe){b=!0,z(),f=Hr(K,n,Pe),Ue.error(Pe)},complete:function(){v=!0,z(),f=Hr(K,s),Ue.complete()}}),I(C).subscribe(l))})(p)}}function Hr(e,t){for(var r=[],o=2;oe.next(document)),e}function q(e,t=document){return Array.from(t.querySelectorAll(e))}function U(e,t=document){let r=se(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function se(e,t=document){return t.querySelector(e)||void 0}function Re(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}var na=_(h(document.body,"focusin"),h(document.body,"focusout")).pipe(ke(1),V(void 0),m(()=>Re()||document.body),J(1));function Zt(e){return na.pipe(m(t=>e.contains(t)),X())}function Je(e){return{x:e.offsetLeft,y:e.offsetTop}}function Uo(e){return _(h(window,"load"),h(window,"resize")).pipe(Ce(0,Oe),m(()=>Je(e)),V(Je(e)))}function er(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return _(h(e,"scroll"),h(window,"resize")).pipe(Ce(0,Oe),m(()=>er(e)),V(er(e)))}function Do(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Do(e,r)}function T(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Do(o,n);return o}function tr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ht(e){let t=T("script",{src:e});return H(()=>(document.head.appendChild(t),_(h(t,"load"),h(t,"error").pipe(E(()=>Mr(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),A(()=>document.head.removeChild(t)),xe(1))))}var Vo=new x,ia=H(()=>typeof ResizeObserver=="undefined"?ht("https://unpkg.com/resize-observer-polyfill"):j(void 0)).pipe(m(()=>new ResizeObserver(e=>{for(let t of e)Vo.next(t)})),E(e=>_(Ve,j(e)).pipe(A(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return ia.pipe(w(t=>t.observe(e)),E(t=>Vo.pipe(M(({target:r})=>r===e),A(()=>t.unobserve(e)),m(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function zo(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var qo=new x,aa=H(()=>j(new IntersectionObserver(e=>{for(let t of e)qo.next(t)},{threshold:0}))).pipe(E(e=>_(Ve,j(e)).pipe(A(()=>e.disconnect()))),J(1));function rr(e){return aa.pipe(w(t=>t.observe(e)),E(t=>qo.pipe(M(({target:r})=>r===e),A(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function Ko(e,t=16){return dt(e).pipe(m(({y:r})=>{let o=he(e),n=bt(e);return r>=n.height-o.height-t}),X())}var or={drawer:U("[data-md-toggle=drawer]"),search:U("[data-md-toggle=search]")};function Qo(e){return or[e].checked}function Ke(e,t){or[e].checked!==t&&or[e].click()}function We(e){let t=or[e];return h(t,"change").pipe(m(()=>t.checked),V(t.checked))}function sa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function ca(){return _(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(V(!1))}function Yo(){let e=h(window,"keydown").pipe(M(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:Qo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),M(({mode:t,type:r})=>{if(t==="global"){let o=Re();if(typeof o!="undefined")return!sa(o,r)}return!0}),le());return ca().pipe(E(t=>t?L:e))}function pe(){return new URL(location.href)}function ot(e,t=!1){if(te("navigation.instant")&&!t){let r=T("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Bo(){return new x}function Go(){return location.hash.slice(1)}function nr(e){let t=T("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function pa(e){return _(h(window,"hashchange"),e).pipe(m(Go),V(Go()),M(t=>t.length>0),J(1))}function Jo(e){return pa(e).pipe(m(t=>se(`[id="${t}"]`)),M(t=>typeof t!="undefined"))}function Fr(e){let t=matchMedia(e);return Xt(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function Xo(){let e=matchMedia("print");return _(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(V(e.matches))}function jr(e,t){return e.pipe(E(r=>r?t():L))}function ir(e,t){return new P(r=>{let o=new XMLHttpRequest;o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network Error"))}),o.addEventListener("abort",()=>{r.error(new Error("Request aborted"))}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let i=Number(o.getResponseHeader("Content-Length"))||0;t.progress$.next(n.loaded/i*100)}}),t.progress$.next(5)),o.send()})}function Ne(e,t){return ir(e,t).pipe(E(r=>r.text()),m(r=>JSON.parse(r)),J(1))}function Zo(e,t){let r=new DOMParser;return ir(e,t).pipe(E(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),J(1))}function en(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function tn(){return _(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(en),V(en()))}function rn(){return{width:innerWidth,height:innerHeight}}function on(){return h(window,"resize",{passive:!0}).pipe(m(rn),V(rn()))}function nn(){return B([tn(),on()]).pipe(m(([e,t])=>({offset:e,size:t})),J(1))}function ar(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=B([o,r]).pipe(m(()=>Je(e)));return B([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function la(e){return h(e,"message",t=>t.data)}function ma(e){let t=new x;return t.subscribe(r=>e.postMessage(r)),t}function an(e,t=new Worker(e)){let r=la(t),o=ma(t),n=new x;n.subscribe(o);let i=o.pipe(Z(),re(!0));return n.pipe(Z(),qe(r.pipe(Y(i))),le())}var fa=U("#__config"),vt=JSON.parse(fa.textContent);vt.base=`${new URL(vt.base,pe())}`;function me(){return vt}function te(e){return vt.features.includes(e)}function be(e,t){return typeof t!="undefined"?vt.translations[e].replace("#",t.toString()):vt.translations[e]}function Ee(e,t=document){return U(`[data-md-component=${e}]`,t)}function oe(e,t=document){return q(`[data-md-component=${e}]`,t)}function ua(e){let t=U(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>U(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function sn(e){if(!te("announce.dismiss")||!e.childElementCount)return L;if(!e.hidden){let t=U(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new x;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ua(e).pipe(w(r=>t.next(r)),A(()=>t.complete()),m(r=>R({ref:e},r)))})}function da(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function cn(e,t){let r=new x;return r.subscribe(({hidden:o})=>{e.hidden=o}),da(e,t).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))}function ha(e,t){let r=H(()=>B([Uo(e),dt(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=he(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return Zt(e).pipe(E(o=>r.pipe(m(n=>({active:o,offset:n})),xe(+!o||1/0))))}function pn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new x,s=i.pipe(Z(),re(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),rr(e).pipe(Y(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),_(i.pipe(M(({active:a})=>a)),i.pipe(ke(250),M(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Ce(16,Oe)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(Pr(125,Oe),M(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(Y(s),M(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(Y(s),ne(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Re())==null||p.blur()}}),r.pipe(Y(s),M(a=>a===o),ze(125)).subscribe(()=>e.focus()),ha(e,t).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))})}function Wr(e){return T("div",{class:"md-tooltip",id:e},T("div",{class:"md-tooltip__inner md-typeset"}))}function ln(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return T("aside",{class:"md-annotation",tabIndex:0},Wr(t),T("a",{href:r,class:"md-annotation__index",tabIndex:-1},T("span",{"data-md-annotation-id":e})))}else return T("aside",{class:"md-annotation",tabIndex:0},Wr(t),T("span",{class:"md-annotation__index",tabIndex:-1},T("span",{"data-md-annotation-id":e})))}function mn(e){return T("button",{class:"md-clipboard md-icon",title:be("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Nr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,T("del",null,p)," "],[]).slice(0,-1),i=me(),s=new URL(e.location,i.base);te("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=me();return T("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},T("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&T("div",{class:"md-search-result__icon md-icon"}),r>0&&T("h1",null,e.title),r<=0&&T("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return T("span",{class:`md-tag ${p}`},c)}),o>0&&n.length>0&&T("p",{class:"md-search-result__terms"},be("search.result.term.missing"),": ",...n)))}function fn(e){let t=e[0].score,r=[...e],o=me(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreNr(l,1)),...c.length?[T("details",{class:"md-search-result__more"},T("summary",{tabIndex:-1},T("div",null,c.length>0&&c.length===1?be("search.result.more.one"):be("search.result.more.other",c.length))),...c.map(l=>Nr(l,1)))]:[]];return T("li",{class:"md-search-result__item"},p)}function un(e){return T("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>T("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?tr(r):r)))}function Ur(e){let t=`tabbed-control tabbed-control--${e}`;return T("div",{class:t,hidden:!0},T("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function dn(e){return T("div",{class:"md-typeset__scrollwrap"},T("div",{class:"md-typeset__table"},e))}function ba(e){let t=me(),r=new URL(`../${e.version}/`,t.base);return T("li",{class:"md-version__item"},T("a",{href:`${r}`,class:"md-version__link"},e.title))}function hn(e,t){return T("div",{class:"md-version"},T("button",{class:"md-version__current","aria-label":be("select.version")},t.title),T("ul",{class:"md-version__list"},e.map(ba)))}function va(e){return e.tagName==="CODE"?q(".c, .c1, .cm",e):[e]}function ga(e){let t=[];for(let r of va(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function bn(e,t){t.append(...Array.from(e.childNodes))}function sr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of ga(t)){let[,c]=a.textContent.match(/\((\d+)\)/);se(`:scope > li:nth-child(${c})`,e)&&(s.set(c,ln(c,i)),a.replaceWith(s.get(c)))}return s.size===0?L:H(()=>{let a=new x,c=a.pipe(Z(),re(!0)),p=[];for(let[l,f]of s)p.push([U(".md-typeset",f),U(`:scope > li:nth-child(${l})`,e)]);return o.pipe(Y(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?bn(f,u):bn(u,f)}),_(...[...s].map(([,l])=>pn(l,t,{target$:r}))).pipe(A(()=>a.complete()),le())})}function vn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return vn(t)}}function gn(e,t){return H(()=>{let r=vn(e);return typeof r!="undefined"?sr(r,e,t):L})}var yn=Ht(Vr());var xa=0;function En(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return En(t)}}function xn(e){return ye(e).pipe(m(({width:t})=>({scrollable:bt(e).width>t})),ee("scrollable"))}function wn(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new x;if(n.subscribe(({scrollable:s})=>{s&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")}),yn.default.isSupported()&&(e.closest(".copy")||te("content.code.copy")&&!e.closest(".no-copy"))){let s=e.closest("pre");s.id=`__code_${xa++}`,s.insertBefore(mn(s.id),e)}let i=e.closest(".highlight");if(i instanceof HTMLElement){let s=En(i);if(typeof s!="undefined"&&(i.classList.contains("annotate")||te("content.code.annotate"))){let a=sr(s,e,t);return xn(e).pipe(w(c=>n.next(c)),A(()=>n.complete()),m(c=>R({ref:e},c)),qe(ye(i).pipe(m(({width:c,height:p})=>c&&p),X(),E(c=>c?a:L))))}}return xn(e).pipe(w(s=>n.next(s)),A(()=>n.complete()),m(s=>R({ref:e},s)))});return te("content.lazy")?rr(e).pipe(M(n=>n),xe(1),E(()=>o)):o}function ya(e,{target$:t,print$:r}){let o=!0;return _(t.pipe(m(n=>n.closest("details:not([open])")),M(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(M(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Sn(e,t){return H(()=>{let r=new x;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ya(e,t).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}var Tn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var zr,wa=0;function Sa(){return typeof mermaid=="undefined"||mermaid instanceof Element?ht("https://unpkg.com/mermaid@9.4.3/dist/mermaid.min.js"):j(void 0)}function On(e){return e.classList.remove("mermaid"),zr||(zr=Sa().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Tn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),J(1))),zr.subscribe(()=>{e.classList.add("mermaid");let t=`__mermaid_${wa++}`,r=T("div",{class:"mermaid"}),o=e.textContent;mermaid.mermaidAPI.render(t,o,(n,i)=>{let s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})}),zr.pipe(m(()=>({ref:e})))}var Mn=T("table");function Ln(e){return e.replaceWith(Mn),Mn.replaceWith(dn(e)),j({ref:e})}function Ta(e){let t=q(":scope > input",e),r=t.find(o=>o.checked)||t[0];return _(...t.map(o=>h(o,"change").pipe(m(()=>U(`label[for="${o.id}"]`))))).pipe(V(U(`label[for="${r.id}"]`)),m(o=>({active:o})))}function _n(e,{viewport$:t}){let r=Ur("prev");e.append(r);let o=Ur("next");e.append(o);let n=U(".tabbed-labels",e);return H(()=>{let i=new x,s=i.pipe(Z(),re(!0));return B([i,ye(e)]).pipe(Ce(1,Oe),Y(s)).subscribe({next([{active:a},c]){let p=Je(a),{width:l}=he(a);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${l}px`);let f=er(n);(p.xf.x+c.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),B([dt(n),ye(n)]).pipe(Y(s)).subscribe(([a,c])=>{let p=bt(n);r.hidden=a.x<16,o.hidden=a.x>p.width-c.width-16}),_(h(r,"click").pipe(m(()=>-1)),h(o,"click").pipe(m(()=>1))).pipe(Y(s)).subscribe(a=>{let{width:c}=he(n);n.scrollBy({left:c*a,behavior:"smooth"})}),te("content.tabs.link")&&i.pipe(je(1),ne(t)).subscribe(([{active:a},{offset:c}])=>{let p=a.innerText.trim();if(a.hasAttribute("data-md-switching"))a.removeAttribute("data-md-switching");else{let l=e.offsetTop-c.y;for(let u of q("[data-tabs]"))for(let d of q(":scope > input",u)){let v=U(`label[for="${d.id}"]`);if(v!==a&&v.innerText.trim()===p){v.setAttribute("data-md-switching",""),d.click();break}}window.scrollTo({top:e.offsetTop-l});let f=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...f])])}}),i.pipe(Y(s)).subscribe(()=>{for(let a of q("audio, video",e))a.pause()}),Ta(e).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))}).pipe(rt(ae))}function An(e,{viewport$:t,target$:r,print$:o}){return _(...q(".annotate:not(.highlight)",e).map(n=>gn(n,{target$:r,print$:o})),...q("pre:not(.mermaid) > code",e).map(n=>wn(n,{target$:r,print$:o})),...q("pre.mermaid",e).map(n=>On(n)),...q("table:not([class])",e).map(n=>Ln(n)),...q("details",e).map(n=>Sn(n,{target$:r,print$:o})),...q("[data-tabs]",e).map(n=>_n(n,{viewport$:t})))}function Oa(e,{alert$:t}){return t.pipe(E(r=>_(j(!0),j(!1).pipe(ze(2e3))).pipe(m(o=>({message:r,active:o})))))}function Cn(e,t){let r=U(".md-typeset",e);return H(()=>{let o=new x;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Oa(e,t).pipe(w(n=>o.next(n)),A(()=>o.complete()),m(n=>R({ref:e},n)))})}function Ma({viewport$:e}){if(!te("header.autohide"))return j(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Le(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),X()),o=We("search");return B([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),X(),E(n=>n?r:j(!1)),V(!1))}function kn(e,t){return H(()=>B([ye(e),Ma(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),X((r,o)=>r.height===o.height&&r.hidden===o.hidden),J(1))}function Hn(e,{header$:t,main$:r}){return H(()=>{let o=new x,n=o.pipe(Z(),re(!0));return o.pipe(ee("active"),Ge(t)).subscribe(([{active:i},{hidden:s}])=>{e.classList.toggle("md-header--shadow",i&&!s),e.hidden=s}),r.subscribe(o),t.pipe(Y(n),m(i=>R({ref:e},i)))})}function La(e,{viewport$:t,header$:r}){return ar(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=he(e);return{active:o>=n}}),ee("active"))}function $n(e,t){return H(()=>{let r=new x;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=se(".md-content h1");return typeof o=="undefined"?L:La(o,t).pipe(w(n=>r.next(n)),A(()=>r.complete()),m(n=>R({ref:e},n)))})}function Rn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),X()),n=o.pipe(E(()=>ye(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return B([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),X((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function _a(e){let t=__md_get("__palette")||{index:e.findIndex(r=>matchMedia(r.getAttribute("data-md-color-media")).matches)};return j(...e).pipe(ce(r=>h(r,"change").pipe(m(()=>r))),V(e[Math.max(0,t.index)]),m(r=>({index:e.indexOf(r),color:{scheme:r.getAttribute("data-md-color-scheme"),primary:r.getAttribute("data-md-color-primary"),accent:r.getAttribute("data-md-color-accent")}})),J(1))}function Pn(e){let t=T("meta",{name:"theme-color"});document.head.appendChild(t);let r=T("meta",{name:"color-scheme"});return document.head.appendChild(r),H(()=>{let o=new x;o.subscribe(i=>{document.body.setAttribute("data-md-color-switching","");for(let[s,a]of Object.entries(i.color))document.body.setAttribute(`data-md-color-${s}`,a);for(let s=0;s{let i=Ee("header"),s=window.getComputedStyle(i);return r.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(a=>(+a).toString(16).padStart(2,"0")).join("")})).subscribe(i=>t.content=`#${i}`),o.pipe(Se(ae)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")});let n=q("input",e);return _a(n).pipe(w(i=>o.next(i)),A(()=>o.complete()),m(i=>R({ref:e},i)))})}function In(e,{progress$:t}){return H(()=>{let r=new x;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),A(()=>r.complete()),m(o=>({ref:e,value:o})))})}var qr=Ht(Vr());function Aa(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Fn({alert$:e}){qr.default.isSupported()&&new P(t=>{new qr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Aa(U(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>be("clipboard.copied"))).subscribe(e)}function Ca(e){if(e.length<2)return[""];let[t,r]=[...e].sort((n,i)=>n.length-i.length).map(n=>n.replace(/[^/]+$/,"")),o=0;if(t===r)o=t.length;else for(;t.charCodeAt(o)===r.charCodeAt(o);)o++;return e.map(n=>n.replace(t.slice(0,o),""))}function cr(e){let t=__md_get("__sitemap",sessionStorage,e);if(t)return j(t);{let r=me();return Zo(new URL("sitemap.xml",e||r.base)).pipe(m(o=>Ca(q("loc",o).map(n=>n.textContent))),de(()=>L),He([]),w(o=>__md_set("__sitemap",o,sessionStorage,e)))}}function jn(e){let t=se("[rel=canonical]",e);typeof t!="undefined"&&(t.href=t.href.replace("//localhost:","//127.0.0.1:"));let r=new Map;for(let o of q(":scope > *",e)){let n=o.outerHTML;for(let i of["href","src"]){let s=o.getAttribute(i);if(s===null)continue;let a=new URL(s,t==null?void 0:t.href),c=o.cloneNode();c.setAttribute(i,`${a}`),n=c.outerHTML;break}r.set(n,o)}return r}function Wn({location$:e,viewport$:t,progress$:r}){let o=me();if(location.protocol==="file:")return L;let n=cr().pipe(m(l=>l.map(f=>`${new URL(f,o.base)}`))),i=h(document.body,"click").pipe(ne(n),E(([l,f])=>{if(!(l.target instanceof Element))return L;let u=l.target.closest("a");if(u===null)return L;if(u.target||l.metaKey||l.ctrlKey)return L;let d=new URL(u.href);return d.search=d.hash="",f.includes(`${d}`)?(l.preventDefault(),j(new URL(u.href))):L}),le());i.pipe(xe(1)).subscribe(()=>{let l=se("link[rel=icon]");typeof l!="undefined"&&(l.href=l.href)}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),i.pipe(ne(t)).subscribe(([l,{offset:f}])=>{history.scrollRestoration="manual",history.replaceState(f,""),history.pushState(null,"",l)}),i.subscribe(e);let s=e.pipe(V(pe()),ee("pathname"),je(1),E(l=>ir(l,{progress$:r}).pipe(de(()=>(ot(l,!0),L))))),a=new DOMParser,c=s.pipe(E(l=>l.text()),E(l=>{let f=a.parseFromString(l,"text/html");for(let b of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...te("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let z=se(b),K=se(b,f);typeof z!="undefined"&&typeof K!="undefined"&&z.replaceWith(K)}let u=jn(document.head),d=jn(f.head);for(let[b,z]of d)z.getAttribute("rel")==="stylesheet"||z.hasAttribute("src")||(u.has(b)?u.delete(b):document.head.appendChild(z));for(let b of u.values())b.getAttribute("rel")==="stylesheet"||b.hasAttribute("src")||b.remove();let v=Ee("container");return Fe(q("script",v)).pipe(E(b=>{let z=f.createElement("script");if(b.src){for(let K of b.getAttributeNames())z.setAttribute(K,b.getAttribute(K));return b.replaceWith(z),new P(K=>{z.onload=()=>K.complete()})}else return z.textContent=b.textContent,b.replaceWith(z),L}),Z(),re(f))}),le());return h(window,"popstate").pipe(m(pe)).subscribe(e),e.pipe(V(pe()),Le(2,1),M(([l,f])=>l.pathname===f.pathname&&l.hash!==f.hash),m(([,l])=>l)).subscribe(l=>{var f,u;history.state!==null||!l.hash?window.scrollTo(0,(u=(f=history.state)==null?void 0:f.y)!=null?u:0):(history.scrollRestoration="auto",nr(l.hash),history.scrollRestoration="manual")}),e.pipe(Cr(i),V(pe()),Le(2,1),M(([l,f])=>l.pathname===f.pathname&&l.hash===f.hash),m(([,l])=>l)).subscribe(l=>{history.scrollRestoration="auto",nr(l.hash),history.scrollRestoration="manual",history.back()}),c.pipe(ne(e)).subscribe(([,l])=>{var f,u;history.state!==null||!l.hash?window.scrollTo(0,(u=(f=history.state)==null?void 0:f.y)!=null?u:0):nr(l.hash)}),t.pipe(ee("offset"),ke(100)).subscribe(({offset:l})=>{history.replaceState(l,"")}),c}var Dn=Ht(Un());function Vn(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,Dn.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Mt(e){return e.type===1}function pr(e){return e.type===3}function zn(e,t){let r=an(e);return _(j(location.protocol!=="file:"),We("search")).pipe($e(o=>o),E(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:te("search.suggest")}}})),r}function qn({document$:e}){let t=me(),r=Ne(new URL("../versions.json",t.base)).pipe(de(()=>L)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),E(n=>h(document.body,"click").pipe(M(i=>!i.metaKey&&!i.ctrlKey),ne(o),E(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?L:(i.preventDefault(),j(c))}}return L}),E(i=>{let{version:s}=n.get(i);return cr(new URL(i)).pipe(m(a=>{let p=pe().href.replace(t.base,"");return a.includes(p.split("#")[0])?new URL(`../${s}/${p}`,t.base):new URL(i)}))})))).subscribe(n=>ot(n,!0)),B([r,o]).subscribe(([n,i])=>{U(".md-header__topic").appendChild(hn(n,i))}),e.pipe(E(()=>o)).subscribe(n=>{var s;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let a=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(a)||(a=[a]);e:for(let c of a)for(let p of n.aliases.concat(n.version))if(new RegExp(c,"i").test(p)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let a of oe("outdated"))a.hidden=!1})}function Pa(e,{worker$:t}){let{searchParams:r}=pe();r.has("q")&&(Ke("search",!0),e.value=r.get("q"),e.focus(),We("search").pipe($e(i=>!i)).subscribe(()=>{let i=pe();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=Zt(e),n=_(t.pipe($e(Mt)),h(e,"keyup"),o).pipe(m(()=>e.value),X());return B([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),J(1))}function Kn(e,{worker$:t}){let r=new x,o=r.pipe(Z(),re(!0));B([t.pipe($e(Mt)),r],(i,s)=>s).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Ke("search",i)}),h(e.form,"reset").pipe(Y(o)).subscribe(()=>e.focus());let n=U("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),Pa(e,{worker$:t}).pipe(w(i=>r.next(i)),A(()=>r.complete()),m(i=>R({ref:e},i)),J(1))}function Qn(e,{worker$:t,query$:r}){let o=new x,n=Ko(e.parentElement).pipe(M(Boolean)),i=e.parentElement,s=U(":scope > :first-child",e),a=U(":scope > :last-child",e);We("search").subscribe(l=>a.setAttribute("role",l?"list":"presentation")),o.pipe(ne(r),$r(t.pipe($e(Mt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?be("search.result.none"):be("search.result.placeholder");break;case 1:s.textContent=be("search.result.one");break;default:let u=tr(l.length);s.textContent=be("search.result.other",u)}});let c=o.pipe(w(()=>a.innerHTML=""),E(({items:l})=>_(j(...l.slice(0,10)),j(...l.slice(10)).pipe(Le(4),Ir(n),E(([f])=>f)))),m(fn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(ce(l=>{let f=se("details",l);return typeof f=="undefined"?L:h(f,"toggle").pipe(Y(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(M(pr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),A(()=>o.complete()),m(l=>R({ref:e},l)))}function Ia(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=pe();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Yn(e,t){let r=new x,o=r.pipe(Z(),re(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(Y(o)).subscribe(n=>n.preventDefault()),Ia(e,t).pipe(w(n=>r.next(n)),A(()=>r.complete()),m(n=>R({ref:e},n)))}function Bn(e,{worker$:t,keyboard$:r}){let o=new x,n=Ee("search-query"),i=_(h(n,"keydown"),h(n,"focus")).pipe(Se(ae),m(()=>n.value),X());return o.pipe(Ge(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(M(({mode:a})=>a==="search")).subscribe(a=>{switch(a.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(M(pr),m(({data:a})=>a)).pipe(w(a=>o.next(a)),A(()=>o.complete()),m(()=>({ref:e})))}function Gn(e,{index$:t,keyboard$:r}){let o=me();try{let n=zn(o.search,t),i=Ee("search-query",e),s=Ee("search-result",e);h(e,"click").pipe(M(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>Ke("search",!1)),r.pipe(M(({mode:c})=>c==="search")).subscribe(c=>{let p=Re();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of q(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":Ke("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...q(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Re()&&i.focus()}}),r.pipe(M(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Kn(i,{worker$:n});return _(a,Qn(s,{worker$:n,query$:a})).pipe(qe(...oe("search-share",e).map(c=>Yn(c,{query$:a})),...oe("search-suggest",e).map(c=>Bn(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ve}}function Jn(e,{index$:t,location$:r}){return B([t,r.pipe(V(pe()),M(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>Vn(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=T("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Fa(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return B([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),X((i,s)=>i.height===s.height&&i.locked===s.locked))}function Kr(e,o){var n=o,{header$:t}=n,r=eo(n,["header$"]);let i=U(".md-sidebar__scrollwrap",e),{y:s}=Je(i);return H(()=>{let a=new x,c=a.pipe(Z(),re(!0)),p=a.pipe(Ce(0,Oe));return p.pipe(ne(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe($e()).subscribe(()=>{for(let l of q(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=he(f);f.scrollTo({top:u-d/2})}}}),ge(q("label[tabindex]",e)).pipe(ce(l=>h(l,"click").pipe(Se(ae),m(()=>l),Y(c)))).subscribe(l=>{let f=U(`[id="${l.htmlFor}"]`);U(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),Fa(e,r).pipe(w(l=>a.next(l)),A(()=>a.complete()),m(l=>R({ref:e},l)))})}function Xn(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return St(Ne(`${r}/releases/latest`).pipe(de(()=>L),m(o=>({version:o.tag_name})),He({})),Ne(r).pipe(de(()=>L),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),He({}))).pipe(m(([o,n])=>R(R({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return Ne(r).pipe(m(o=>({repositories:o.public_repos})),He({}))}}function Zn(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return Ne(r).pipe(de(()=>L),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),He({}))}function ei(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Xn(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return Zn(r,o)}return L}var ja;function Wa(e){return ja||(ja=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return j(t);if(oe("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return L}return ei(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>L),M(t=>Object.keys(t).length>0),m(t=>({facts:t})),J(1)))}function ti(e){let t=U(":scope > :last-child",e);return H(()=>{let r=new x;return r.subscribe(({facts:o})=>{t.appendChild(un(o)),t.classList.add("md-source__repository--active")}),Wa(e).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}function Na(e,{viewport$:t,header$:r}){return ye(document.body).pipe(E(()=>ar(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function ri(e,t){return H(()=>{let r=new x;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(te("navigation.tabs.sticky")?j({hidden:!1}):Na(e,t)).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}function Ua(e,{viewport$:t,header$:r}){let o=new Map,n=q("[href^=\\#]",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=se(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(ee("height"),m(({height:a})=>{let c=Ee("main"),p=U(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return ye(document.body).pipe(ee("height"),E(a=>H(()=>{let c=[];return j([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),Ge(i),E(([c,p])=>t.pipe(kr(([l,f],{offset:{y:u},size:d})=>{let v=u+d.height>=Math.floor(a.height);for(;f.length;){let[,b]=f[0];if(b-p=u&&!v)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),X((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),V({prev:[],next:[]}),Le(2,1),m(([a,c])=>a.prev.length{let i=new x,s=i.pipe(Z(),re(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),te("toc.follow")){let a=_(t.pipe(ke(1),m(()=>{})),t.pipe(ke(250),m(()=>"smooth")));i.pipe(M(({prev:c})=>c.length>0),Ge(o.pipe(Se(ae))),ne(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=zo(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=he(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return te("navigation.tracking")&&t.pipe(Y(s),ee("offset"),ke(250),je(1),Y(n.pipe(je(1))),Tt({delay:250}),ne(i)).subscribe(([,{prev:a}])=>{let c=pe(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Ua(e,{viewport$:t,header$:r}).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))})}function Da(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),Le(2,1),m(([s,a])=>s>a&&a>0),X()),i=r.pipe(m(({active:s})=>s));return B([i,n]).pipe(m(([s,a])=>!(s&&a)),X(),Y(o.pipe(je(1))),re(!0),Tt({delay:250}),m(s=>({hidden:s})))}function ni(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new x,s=i.pipe(Z(),re(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Y(s),ee("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),Da(e,{viewport$:t,main$:o,target$:n}).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))}function ii({document$:e,tablet$:t}){e.pipe(E(()=>q(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ce(r=>h(r,"change").pipe(Rr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),ne(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Va(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function ai({document$:e}){e.pipe(E(()=>q("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),M(Va),ce(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function si({viewport$:e,tablet$:t}){B([We("search"),t]).pipe(m(([r,o])=>r&&!o),E(r=>j(r).pipe(ze(r?400:100))),ne(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function za(){return location.protocol==="file:"?ht(`${new URL("search/search_index.js",Qr.base)}`).pipe(m(()=>__index),J(1)):Ne(new URL("search/search_index.json",Qr.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var nt=No(),_t=Bo(),gt=Jo(_t),Yr=Yo(),Te=nn(),lr=Fr("(min-width: 960px)"),pi=Fr("(min-width: 1220px)"),li=Xo(),Qr=me(),mi=document.forms.namedItem("search")?za():Ve,Br=new x;Fn({alert$:Br});var Gr=new x;te("navigation.instant")&&Wn({location$:_t,viewport$:Te,progress$:Gr}).subscribe(nt);var ci;((ci=Qr.version)==null?void 0:ci.provider)==="mike"&&qn({document$:nt});_(_t,gt).pipe(ze(125)).subscribe(()=>{Ke("drawer",!1),Ke("search",!1)});Yr.pipe(M(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=se("link[rel=prev]");typeof t!="undefined"&&ot(t);break;case"n":case".":let r=se("link[rel=next]");typeof r!="undefined"&&ot(r);break;case"Enter":let o=Re();o instanceof HTMLLabelElement&&o.click()}});ii({document$:nt,tablet$:lr});ai({document$:nt});si({viewport$:Te,tablet$:lr});var Xe=kn(Ee("header"),{viewport$:Te}),Lt=nt.pipe(m(()=>Ee("main")),E(e=>Rn(e,{viewport$:Te,header$:Xe})),J(1)),qa=_(...oe("consent").map(e=>cn(e,{target$:gt})),...oe("dialog").map(e=>Cn(e,{alert$:Br})),...oe("header").map(e=>Hn(e,{viewport$:Te,header$:Xe,main$:Lt})),...oe("palette").map(e=>Pn(e)),...oe("progress").map(e=>In(e,{progress$:Gr})),...oe("search").map(e=>Gn(e,{index$:mi,keyboard$:Yr})),...oe("source").map(e=>ti(e))),Ka=H(()=>_(...oe("announce").map(e=>sn(e)),...oe("content").map(e=>An(e,{viewport$:Te,target$:gt,print$:li})),...oe("content").map(e=>te("search.highlight")?Jn(e,{index$:mi,location$:_t}):L),...oe("header-title").map(e=>$n(e,{viewport$:Te,header$:Xe})),...oe("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?jr(pi,()=>Kr(e,{viewport$:Te,header$:Xe,main$:Lt})):jr(lr,()=>Kr(e,{viewport$:Te,header$:Xe,main$:Lt}))),...oe("tabs").map(e=>ri(e,{viewport$:Te,header$:Xe})),...oe("toc").map(e=>oi(e,{viewport$:Te,header$:Xe,main$:Lt,target$:gt})),...oe("top").map(e=>ni(e,{viewport$:Te,header$:Xe,main$:Lt,target$:gt})))),fi=nt.pipe(E(()=>Ka),qe(qa),J(1));fi.subscribe();window.document$=nt;window.location$=_t;window.target$=gt;window.keyboard$=Yr;window.viewport$=Te;window.tablet$=lr;window.screen$=pi;window.print$=li;window.alert$=Br;window.progress$=Gr;window.component$=fi;})(); +//# sourceMappingURL=bundle.6c14ae12.min.js.map + diff --git a/assets/javascripts/bundle.6c14ae12.min.js.map b/assets/javascripts/bundle.6c14ae12.min.js.map new file mode 100644 index 000000000..49396ad0b --- /dev/null +++ b/assets/javascripts/bundle.6c14ae12.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/rxjs/node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2023 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an