Tags

At its simplest, bdns-plus can be used to build tags from data. The simple_tag function takes a dictionary of data and a TagDef object, and returns a tag string. It is directly analogous to the way that tags work in Revit, and the TagDef class is a direct port of the Revit tag definition.

The example below shows how an instance tag can be built from a dictionary of data.

Default Tag Definitions

The default tag definitions are available in the ConfigTags class.

Type Tag

Default Tag Definition for indentifying a unique type of equipment, there is likely to be many instances of a type in the building. Expected to be used when a unique reference to every item is not required. For example, a light fitting type that may be used in many locations.

Required: [abbreviation]

Allowed: [abbreviation][type_reference]/[type_extra]

Instance Tag

Default Tag Definition for indentifying a unique instance of equipment within a building. Expected to be used for adding equipment references to drawings, reports and legends.

Required: [abbreviation]/[volume]/[level]/[volume_level_instance]

Allowed: [abbreviation]/[volume]/[level]/[volume_level_instance]/[instance_extra]

BDNS Tag

TagDef Definition in accordance with Building Data Naming System

Required: [abbreviation]-[instance_reference]

Allowed: [country]-[city]-[project]-[abbreviation]-[instance_reference]_[instance_extra]

The instance reference for the BDNS tag is constructed from volume and level data as follows:

  • Volumes are represented by 1no integer digits (volume_id).
  • Levels are represented by 2no integer digits (level_id).
  • An enumerating integer value is added to ensure uniqueness for a given floor / level (volume_level_instance).
  • These numbers are joined without delimiter to create a unique number for a given abbreviation:
    • [volume_id][level_id][volume_level_instance]

Custom Tag Definitions

Tip

Refer to reformat-type-and-instance-tags for a more comprehensive example.

Code
tag_def = {
    "name": "Equipment Instances",
    "description": "a example tag definition with different formatting",
    "fields": [
        {
            "field_name": "abbreviation",
            "field_aliases": [
                "Abbreviation",
            ],
            "allow_none": False,
            "prefix": "",
            "suffix": "",
            "zfill": None,
            "regex": None,
            "validator": None,
        },
        {
            "field_name": "volume",
            "field_aliases": [
                "Volume",
            ],
            "allow_none": False,
            "prefix": ".",
            "suffix": "",
            "zfill": None,
            "regex": None,
            "validator": None,
        },
        {
            "field_name": "level",
            "field_aliases": [
                "Level",
            ],
            "allow_none": False,
            "prefix": ".",
            "suffix": "",
            "zfill": 2,
            "regex": None,
            "validator": None,
        },
        {
            "field_name": "volume_level_instance",
            "field_aliases": [
                "VolumeLevelInstance",
            ],
            "allow_none": False,
            "prefix": ".",
            "suffix": "",
            "zfill": 2,
            "regex": None,
            "validator": None,
        },
    ],
}

itag_def = TagDef(**tag_def)

data = {"abbreviation": "AHU", "level": "GF", "volume_level_instance": 1, "volume": "N"}
tag_string = simple_tag(data, tag=itag_def)
json_str = data_as_json_markdown(itag_def.model_dump(mode="json"))


title = "Tag Definition as json data. Can be loaded dynamically and configured per project."
display(
    Markdown(markdown_callout(json_str, title=title)),
)
display(Markdown(summarise_tag_config(itag_def)))  # This will print the tag configuration summary
display(Markdown("**Example:**"))  # This will print the tag string
display(Markdown(data_as_yaml_markdown(data)))  # This will print the data as YAML markdown
display(Markdown(f"**Tag String:** `{tag_string}`"))  # This will print the tag string
{
    "name": "Equipment Instances",
    "description": "a example tag definition with different formatting",
    "fields": [
        {
            "field_name": "abbreviation",
            "field_aliases": [
                "Abbreviation"
            ],
            "allow_none": false,
            "prefix": "",
            "suffix": "",
            "zfill": null,
            "regex": null,
            "validator": null
        },
        {
            "field_name": "volume",
            "field_aliases": [
                "Volume"
            ],
            "allow_none": false,
            "prefix": ".",
            "suffix": "",
            "zfill": null,
            "regex": null,
            "validator": null
        },
        {
            "field_name": "level",
            "field_aliases": [
                "Level"
            ],
            "allow_none": false,
            "prefix": ".",
            "suffix": "",
            "zfill": 2,
            "regex": null,
            "validator": null
        },
        {
            "field_name": "volume_level_instance",
            "field_aliases": [
                "VolumeLevelInstance"
            ],
            "allow_none": false,
            "prefix": ".",
            "suffix": "",
            "zfill": 2,
            "regex": null,
            "validator": null
        }
    ]
}

Equipment Instances

a example tag definition with different formatting

Required: [abbreviation].[volume].[level].[volume_level_instance]

Allowed: [abbreviation].[volume].[level].[volume_level_instance]

Example:

abbreviation: AHU
level: GF
volume_level_instance: 1
volume: N

Tag String: AHU.N.GF.01

Custom Tags for Specific Equipment Types

Tip

Refer to custom-nomenclature-for-vent-equipment for a more comprehensive example.

not reccommended

Ideally tags should be consistent across all equipment types.

The bdns-tag, type-tag and instance-tag and generated for every item of equipment in the project, and are globally defined and unique for the project.

In an ideal world, the way that tags are constructed is consistent across all equipment types. However projects are messy, there are cases where specific equipment types require custom tags. In these cases it is possible to define custom tags with custom scopes.

How to Define the Scope of a Custom Tag

A RuleSet can be used to define the scope of a custom tag. (For more info refer to pyrulefilter. It allows the user to define a set of rules that are used to check the equipment data, and returns a boolean value indicating if a RuleSet matches the data. At Max Fordham, RuleSets are also used to define what equipment should be shown in a schedule, so by extension it is then possible to apply custom tags to every equipment item shown within a schedule.

RuleSet Definition

Code
import jsonschema2md
from IPython.display import Markdown
from pyrulefilter import RuleSet

parser = jsonschema2md.Parser(
    examples_as_yaml=False,
    show_examples="all",
)
md_lines = parser.parse_schema(RuleSet.model_json_schema())
Markdown("".join(md_lines[2:]))
  • FilterCategoriesEnum (string): Must be one of: ["Analytical Spaces", "Analytical Surfaces", "Area Loads", "Areas", "Assemblies", "Audio Visual Devices", "Cable Trays", "Cable Tray Fittings", "Callouts", "Communication Devices", "Conduits", "Conduit Fittings", "Data Devices", "Detail Items", "Duct Accessories", "Ducts", "Duct Fittings", "Duct Insulations", "Duct Linings", "Duct Systems", "Air Terminals", "Electrical Equipment", "Electrical Fixtures", "MEP Fabrication Containment", "MEP Fabrication Ductwork", "Insulation", "Lining", "MEP Fabrication Hangers", "MEP Fabrication Pipework", "Insulation", "Fire Alarm Devices", "Fire Protection", "Flex Ducts", "Flex Pipes", "Analytical Floors", "Food Service Equipment", "Generic Models", "Multi-segmented Grid", "Grids", "Gutters", "HVAC Zones", "Lighting Devices", "Lighting Fixtures", "Analytical Links", "Air Systems", "Water Loops", "Spaces", "System-Zones", "Mechanical Equipment", "Medical Equipment", "Nurse Call Devices", "Pipe Accessories", "Pipes", "Pipe Fittings", "Pipe Insulations", "Piping Systems", "Duct Placeholders", "Pipe Placeholders", "Plumbing Fixtures", "Point Loads", "Reference Lines", "Rooms", "Sections", "Security Devices", "Sprinklers", "Switch System", "Telephone Devices", "Analytical Walls", "Zone Equipment"].
  • OperatorsEnum (string): Must be one of: ["begins with", "contains", "ends with", "equals", "does not begin with", "does not contain", "does not end with", "does not equal"].
  • Rule (object)
    • categories (array): Revit MEP categories to filter by (i.e. revit object must belong to categories defined here). If empty, all categories are included. Default: [].
    • parameter (string, required): name of schedule parameter against which to apply filter rule.
    • operator: logical operator used to evaluate parameter value against value below. Refer to #/$defs/OperatorsEnum.
    • value (string): Value to filter by. Evaluates to the appropriate type. Leave empty if none required (e.g. has value operator). Default: "".
  • RuleSet (object): A set of rules that defines what equipment specifications will appear in a given schedule.
    Rules must evaluate to True for the item to be included in a schedule. This is analogous to how filter rules work in Revit.
    As such, rules defined are imported into Revit and are used to create Revit Schedules.

    .
    • set_type: Refer to #/$defs/RuleSetType. Default: "OR".
    • rule (array, required): rules return a boolean for the logical evaluation defined below for every item within the categories defined .
  • RuleSetType (string): Must be one of: ["AND", "OR"].

Example

In this example, we show how a rule can be defined to give specific equipment a different tag if the data that defines that equipment matches a rule.

The Exammple below shows ventilation equipment tagged in the normal way, but we’ll assume that AHUs must be named by their volume only (lets pretend that the project is a refurbishment and the AHU names already exist).

In the code below you can see how a ruleset is defined to filter out the equipment requiring a custom tag.

Code
import pandas as pd
import polars as pl
from pyrulefilter import Rule, RuleSet, ruleset_check_dicts

from bdns_plus.docs import display_tag_data, get_idata_tag_table, get_tags, get_vent_equipment
from bdns_plus.gen_idata import batch_gen_idata, gen_config_iref
from bdns_plus.models import Config, CustomTagDef, GenDefinition, TagDef


def get_idata_tag_df(header: list[tuple], idata: list[dict]) -> pd.DataFrame:
    annotated_cols = pd.MultiIndex.from_tuples(header)
    df_tags = pd.DataFrame(idata).sort_values(by=["level"]).reset_index(drop=True)
    df_tags.columns = annotated_cols
    return df_tags


LEVEL_MIN, LEVEL_MAX, NO_VOLUMES = -1, 3, 2
config_iref = gen_config_iref(level_min=LEVEL_MIN, level_max=LEVEL_MAX, no_volumes=NO_VOLUMES)
idata = get_vent_equipment(config_iref)

r = Rule(
    parameter="abbreviation",
    operator="equals",
    value="AHU",
)
rule_set = RuleSet(rule=[r], set_type="OR")
custom_tag_def = TagDef(
    name="Custom AHU Instance Tag",
    fields=[
        TagField(field_name="abbreviation", suffix="-"),
        TagField(field_name="volume", prefix="Volume"),
    ],
)
custom_tag = CustomTagDef(
    description="Custom AHU Tag",
    i_tag=custom_tag_def,
    t_tag=custom_tag_def,  # just override it to be the same
    scope=rule_set,
)
config = Config(custom_tags=[custom_tag])
data = [
    {"section": "vent", "is_custom": "False"} | x.model_dump(mode="json") | get_tags(x, config=config) for x in idata
]  # TODO: generalise this into fn in docs.py
di_arrays = {key: [d.get(key, None) for d in data] for key in data[0]}
di_arrays = {k: [str(x) if x is not None else "" for x in v] for k, v in di_arrays.items()}
df_tags = pl.DataFrame(di_arrays)
df_tags = df_tags.drop("uniclass_ss")
display_tag_data(df_tags)
Equipment Tags
Projects contain multiple identical (equipment) type_tag's. bdns_tag's and instance_tag's are unique.
section generated outputs type inputs instance inputs
type_tag instance_tag bdns_tag abbreviation type_reference type_extra volume level volume_level_instance instance_extra
vent AHU-Volume1 AHU-Volume1 AHU-1031 AHU 1 1 3 1
vent AHU-Volume2 AHU-Volume2 AHU-2031 AHU 1 2 3 1
vent MVHR1 MVHR/1/-1/1 MVHR-1991 MVHR 1 1 -1 1
vent TEF1 TEF/1/-1/1 TEF-1991 TEF 1 1 -1 1
vent MVHR1 MVHR/1/0/1 MVHR-1001 MVHR 1 1 0 1
vent TEF1 TEF/1/0/1 TEF-1001 TEF 1 1 0 1
vent MVHR1 MVHR/1/1/1 MVHR-1011 MVHR 1 1 1 1
vent TEF1 TEF/1/1/1 TEF-1011 TEF 1 1 1 1
vent MVHR1 MVHR/1/2/1 MVHR-1021 MVHR 1 1 2 1
vent TEF1 TEF/1/2/1 TEF-1021 TEF 1 1 2 1
vent MVHR1 MVHR/1/3/1 MVHR-1031 MVHR 1 1 3 1
vent TEF1 TEF/1/3/1 TEF-1031 TEF 1 1 3 1
vent MVHR1 MVHR/2/-1/1 MVHR-2991 MVHR 1 2 -1 1
vent TEF1 TEF/2/-1/1 TEF-2991 TEF 1 2 -1 1
vent MVHR1 MVHR/2/0/1 MVHR-2001 MVHR 1 2 0 1
vent TEF1 TEF/2/0/1 TEF-2001 TEF 1 2 0 1
vent MVHR1 MVHR/2/1/1 MVHR-2011 MVHR 1 2 1 1
vent TEF1 TEF/2/1/1 TEF-2011 TEF 1 2 1 1
vent MVHR1 MVHR/2/2/1 MVHR-2021 MVHR 1 2 2 1
vent TEF1 TEF/2/2/1 TEF-2021 TEF 1 2 2 1
vent MVHR1 MVHR/2/3/1 MVHR-2031 MVHR 1 2 3 1
vent TEF1 TEF/2/3/1 TEF-2031 TEF 1 2 3 1
vent KEF1 KEF/1/0/1 KEF-1001 KEF 1 1 0 1
vent KEF2 KEF/1/3/1 KEF-1031 KEF 2 1 3 1
Yellow highlighting rows indicate rows that have custom tags.