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.
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.
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 summarydisplay(Markdown("**Example:**")) # This will print the tag stringdisplay(Markdown(data_as_yaml_markdown(data))) # This will print the data as YAML markdowndisplay(Markdown(f"**Tag String:** `{tag_string}`")) # This will print the tag string
Tag Definition as json data. Can be loaded dynamically and configured per project.
{"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
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.
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.
.
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 pdimport polars as plfrom pyrulefilter import Rule, RuleSet, ruleset_check_dictsfrom bdns_plus.docs import display_tag_data, get_idata_tag_table, get_tags, get_vent_equipmentfrom bdns_plus.gen_idata import batch_gen_idata, gen_config_ireffrom bdns_plus.models import Config, CustomTagDef, GenDefinition, TagDefdef 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_colsreturn df_tagsLEVEL_MIN, LEVEL_MAX, NO_VOLUMES =-1, 3, 2config_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.pydi_arrays = {key: [d.get(key, None) for d in data] for key in data[0]}di_arrays = {k: [str(x) if x isnotNoneelse""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.