Combining data from the symbol and the instance
⚠️ Advanced composition pattern
This chapter combines multiple advanced concepts: symbol indexing, instance enrichment, shared-parameter access, and domain-based filtering.
Use this pattern only after understanding the earlier chapters.
This filter combines the data of the symbol and the instance to one single object.
(
/* All relevant GUIDs in one place */
$paramGuids := {
"NLRS_C_SfB_tabel_1_code": "7b4acbe7-dcd9-4e30-85a5-98bc8a746427"
};
/* Build one lookup table with metadata per GUID */
$paramMetaByGuid := $merge(
$$[type = "Parameter" and values.guid in $paramGuids.*].{
$string(values.guid): {
"paramExist": true,
"guid": values.guid,
"name": values.name
}
}
);
/* Shared parameter helper */
$getSharedParam := function($object, $logicalName){
(
$guid := $lookup($paramGuids, $logicalName);
$meta := $guid ? $lookup($paramMetaByGuid, $string($guid)) : undefined;
$sp := $guid and $exists($object.values)
? $lookup($object.values, "p_" & $guid)
: undefined;
{
"exists": $exists($sp),
"value": $exists($sp) ? $sp.value : null,
"valueAsString": $exists($sp) ? $sp.valueAsString : null,
"guid": $meta ? $meta.guid : $guid,
"name": $meta ? $meta.name : $logicalName
}
)
};
/* AssemblyCode inclusion regex */
$assemblyCodeIncluded := /^5/i;
/* Category Exclusion */
$CategoryExclusion := ["OST_DetailComponents"];
/* Filter FamilySymbols */
$symbols :=
$[
type = "FamilySymbol"
and values.category.type = "Model"
and $not(values.category.label in $CategoryExclusion)
and $string(values.assemblyCode) ~> $assemblyCodeIncluded
];
/* Index symbols by id */
$symIndex := $merge($symbols.{$string(id): $});
/* Main query over FamilyInstances */
$[type = "FamilyInstance" and $exists(parent)].(
$sym := $lookup($symIndex, $string(parent.id));
$sym ?
{
"id": id,
"type": type,
"name": name,
"Category": $sym.values.category.label,
"assemblyCode": $sym.values.assemblyCode,
"NLRS_C_SfB_tabel_1_code": $getSharedParam($, "NLRS_C_SfB_tabel_1_code")
}
: ()
)
)
getting data from FamilySymbol into FamilyInstance
This rule is the canonical example of something users must understand:
Not all data lives on the instance.
What lives where (Revit reality)
| Object | Typical data |
|---|---|
| Family | Editable, name, category |
| FamilySymbol (Type) | Assembly Code, Category, Type parameters |
| FamilyInstance | Location, Level, Instance parameters |
Assembly Code does not belong to the instance — it belongs to the type.
That’s why this rule works in two phases:
Phase 1 — filter and collect FamilySymbols
$symbols := $[type = "FamilySymbol" ...];
This defines scope:
“Which types are relevant for this rule?”
Phase 2 — index symbols by ID
$symIndex := $merge($symbols.{$string(id): $});
This creates a lookup table:
- Key = FamilySymbol ID
- Value = full FamilySymbol object
This avoids expensive repeated searches.
Phase 3 — enrich FamilyInstances
$sym := $lookup($symIndex, $string(parent.id));
Here’s the key concept:
A FamilyInstance references its type via
parent.id
So now you can safely do:
$sym.values.assemblyCode
$sym.values.category.label
This is how type data flows into instance-level validation.
Important rule of thumb for users
This is the line you want to explicitly teach:
You must know where the data lives before you build your filter.
- If the data is on the type, filter types first
- If the data is on the instance, filter instances directly
- If the data is on ProjectInfo / Parameter, resolve it separately
Guessing leads to broken rules.
Copy–paste is not cheating
Once a rule like this works:
✔ Symbol filtering ✔ Symbol indexing ✔ Instance enrichment ✔ Shared parameter extraction
Users should copy it and adapt:
- Change the category logic
- Change the Assembly Code regex
- Change
$paramGuids - Add or remove output fields
That is how consistent, maintainable rules are built.