Node artifacts rule

TypeScript type: NodeArtifactsRule.

Note: The rule is costly performance-wise and should only be used in very limited amount of specific cases where hidden child nodes need to be used to determine parent node's visibility.

Node artifacts rules are used to create and assign artifacts to specific nodes. The artifacts can be accessed when evaluating parent node's hideExpression to decide whether it should be hidden or not.

Typical use case

The hierarchy consists of Subject nodes and each Subject may or may not have child Model nodes. There are 2 types of Models: A & B, we want ModelA nodes to be visible and ModelB ones to be hidden. We want Subject node to be visible only if it has a Model (either A or B).

Problem

In this case we can't use hideIfNoChildren flag on Subjects, because a Subject node may only have a related ModelB which means Subject doesn't have children and should be displayed as a leaf node.

Solution

Use NodeArtifacts on the ModelB nodes and a hideExpression on Subject nodes. The expression can access artifacts created by child ModelB nodes: NOT ThisNode.HasChildren AND NOT ThisNode.ChildrenArtifacts.AnyMatches(x => x.IsModelB).

Attributes

Name Required? Type Default
Filtering
condition No ECExpression ""
requiredSchemas No RequiredSchemaSpecification[] []
priority No number 1000
onlyIfNotHandled No boolean false
Artifacts
items Yes { [key: string]: ECExpression }

Attribute: condition

Specifies an ECExpression that allows applying node artifacts based on evaluation result, e.g. by some property of the parent node.

Type ECExpression
Is Required No
Default Value ""
// The ruleset has a root nodes rule that returns `bis.Model` nodes only if their child node
// artifacts contain an artifact "IsSpecialChild". There's also a child nodes rule that produces
// hidden child nodes for `bis.Model` and `bis.GeometricElement3d` nodes have the "IsSpecialChild" artifact value
// set to `true`. This means only `bis.GeometricModel3d` nodes should be displayed as root nodes (no other
// type of `bis.Model` should have `bis.GeometricElement3d` elements).
const ruleset: Ruleset = {
  id: "example",
  rules: [{
    ruleType: "RootNodes",
    specifications: [{
      specType: "InstanceNodesOfSpecificClasses",
      classes: [{ schemaName: "BisCore", classNames: ["Model"], arePolymorphic: true }],
      hideExpression: `NOT ThisNode.ChildrenArtifacts.AnyMatches(x => x.IsSpecialChild)`,
      groupByClass: false,
      groupByLabel: false,
    }],
  }, {
    ruleType: "ChildNodes",
    condition: `ParentNode.IsOfClass("Model", "BisCore")`,
    specifications: [{
      specType: "RelatedInstanceNodes",
      relationshipPaths: [{
        relationship: { schemaName: "BisCore", className: "ModelContainsElements" },
        direction: "Forward",
      }],
      hideNodesInHierarchy: true,
      groupByClass: false,
      groupByLabel: false,
    }],
    customizationRules: [{
      ruleType: "NodeArtifacts",
      condition: `ThisNode.IsOfClass("GeometricElement3d", "BisCore")`,
      items: {
        ["IsSpecialChild"]: `TRUE`,
      },
    }],
  }],
};
// Confirm we get only the GeometricModel3d
const nodes = await Presentation.presentation.getNodes({ imodel, rulesetOrId: ruleset });
expect(nodes).to.have.lengthOf(1).and.containSubset([{
  key: { instanceKeys: [{ className: "BisCore:PhysicalModel" }] },
  hasChildren: undefined,
}]);

Attribute: requiredSchemas

A list of ECSchema requirements that need to be met for the rule to be used.

Type RequiredSchemaSpecification[]
Is Required No
Default Value []
// The ruleset has one root node rule that returns `bis.ExternalSourceAspect` instances. The
// ECClass was introduced in BisCore version 1.0.2, so the rule needs a `requiredSchemas` attribute
// to only use the rule if the version meets the requirement.
const ruleset: Ruleset = {
  id: "example",
  rules: [{
    ruleType: "RootNodes",
    requiredSchemas: [{ name: "BisCore", minVersion: "1.0.2" }],
    specifications: [{
      specType: "InstanceNodesOfSpecificClasses",
      classes: [{
        schemaName: "BisCore",
        classNames: ["ExternalSourceAspect"],
      }],
    }],
  }],
};

Attribute: priority

Defines the order in which rules are handled, higher number means the rule is handled first. If priorities are equal, the rules are handled in the order they're defined. The attribute may be especially useful when combined with onlyIfNotHandled attribute.

Type number
Is Required No
Default Value 1000
// The ruleset has two root node rules that return nodes "A" and "B" respectively. The rules
// have different priorities and higher priority rule is handled first - it's node appears first.
const ruleset: Ruleset = {
  id: "example",
  rules: [{
    ruleType: "RootNodes",
    priority: 1,
    specifications: [{
      specType: "CustomNode",
      type: "A",
      label: "A",
    }],
  }, {
    ruleType: "RootNodes",
    priority: 2,
    specifications: [{
      specType: "CustomNode",
      type: "B",
      label: "B",
    }],
  }],
};

Example of using "priority" attribute

Attribute: onlyIfNotHandled

When true, the rule takes effect only when all other node artifact rules with higher priority are ruled out. This attribute is most useful for defining fallback rules.

Type boolean
Is Required No
Default Value false
// The ruleset has two root node rules that return nodes "A" and "B" respectively. The "A" rule has
// lower priority and `onlyIfNotHandled` attribute, which allows it to be overriden by higher priority rules.
// The "B" rule does exactly that.
const ruleset: Ruleset = {
  id: "example",
  rules: [{
    ruleType: "RootNodes",
    priority: 1,
    onlyIfNotHandled: true,
    specifications: [{
      specType: "CustomNode",
      type: "A",
      label: "A",
    }],
  }, {
    ruleType: "RootNodes",
    priority: 2,
    specifications: [{
      specType: "CustomNode",
      type: "B",
      label: "B",
    }],
  }],
};

Example of using "onlyIfNotHandled" attribute

Attribute: items

A map of ECExpressions whose evaluation results are used as artifact values.

Type { [key: string]: ECExpression }
Is Required Yes
// The ruleset has a root nodes rule that returns `bis.Model` nodes only if their child node
// artifacts contain an artifact "IsSpecialChild". There's also a child nodes rule that produces
// hidden child nodes for `bis.Model` and the nodes have a calculated "IsSpecialChild" artifact value
// that only evaluates to `true` for `bis.GeometricElement3d` elements. This means only `bis.GeometricModel3d`
// models should be displayed as root nodes (no other type of `bis.Model` should have `bis.GeometricElement3d`
// elements).
const ruleset: Ruleset = {
  id: "example",
  rules: [{
    ruleType: "RootNodes",
    specifications: [{
      specType: "InstanceNodesOfSpecificClasses",
      classes: [{ schemaName: "BisCore", classNames: ["Model"], arePolymorphic: true }],
      hideExpression: `NOT ThisNode.ChildrenArtifacts.AnyMatches(x => x.IsSpecialChild)`,
      groupByClass: false,
      groupByLabel: false,
    }],
  }, {
    ruleType: "ChildNodes",
    condition: `ParentNode.IsOfClass("Model", "BisCore")`,
    specifications: [{
      specType: "RelatedInstanceNodes",
      relationshipPaths: [{
        relationship: { schemaName: "BisCore", className: "ModelContainsElements" },
        direction: "Forward",
      }],
      hideNodesInHierarchy: true,
      groupByClass: false,
      groupByLabel: false,
    }],
    customizationRules: [{
      ruleType: "NodeArtifacts",
      items: {
        ["IsSpecialChild"]: `this.IsOfClass("GeometricElement3d", "BisCore")`,
      },
    }],
  }],
};
// Confirm we get only the GeometricModel3d
const nodes = await Presentation.presentation.getNodes({ imodel, rulesetOrId: ruleset });
expect(nodes).to.have.lengthOf(1).and.containSubset([{
  key: { instanceKeys: [{ className: "BisCore:PhysicalModel" }] },
  hasChildren: undefined,
}]);

Last Updated: 24 January, 2023