You can configure overrides to be scoped “perParent,” so that when a user edits the override, they are only editing the values for one parent grouping at a time. For example, you might group overrides by a level parent, so that you’re only editing the objects on that level at one time.
This capability can be quite complex! A full implementation supporting view-associated parents and add overrides has three requisite steps:
Quick Start
- In the child Element’s schema define its
$hyparRelationship
to a parent Element. In this example the child Element isToy
, defined inhttps://schemas.hypar.io/Toy.json
:
json{ "x-namespace": "Elements", "required": [], "properties": { "ToyBox": { "type": [ "string", "null" ], "format": "uuid", "$hyparRelationship": { "type": "child-of", "discriminators": [ "Elements.ToyBox" ] } } }, "additionalProperties": false, "description": "A toy", "allOf": [ { "$ref": "https://schemas.hypar.io/GeometricElement.json" } ] }
- In the
"overrides": {..}
section of the associated Function’shypar.json
: - Include the
perParent
entry, whose value references a property uniquely identifying the parent Element. - Include a
$hyparRelation
entry that will tell the associated function the identity of the parent to which new Elements have been added:
json"overrides": { "Toys": { "perParent": "ToyBox", "context": "[*discriminator=Elements.Toy]", "identity": { "Name": { "type": "string" } }, "behaviors": { "add": true }, "schema": { "ToyBox": { "$hyparRelation": { "$ref": "https://schemas.hypar.io/ToyBox.json" }, "type": "object", "properties": { "Name": { "type": "string" } } }, "Transform": { "$ref": "https://schemas.hypar.io/Transform.json" } } } },
- In your
function code
: - Implement the override logic to set the parent property on each overridden child element.
javascriptvar toysOverridden = input.Overrides.Toys.CreateElements( input.Overrides.Additions.Toys, (add) => new Toy { Transform = add.Value.Transform, ToyBox = add.Value.ToyBox.Name == "Box 1" ? toyBox1.Id : toyBox2.Id }, (elem, identity) => identity.Name == elem.Name, (elem, edit) => { elem.Transform = edit.Value.Transform; elem.ToyBox = edit.Value.ToyBox.Name == "Box 1" ? toyBox1.Id : toyBox2.Id; return elem; }, toys);
Detailed Implementation
Configuration with perParent
and $hyparRelationship
To set up an override to be “perParent,” you need to set a
perParent
property in the override specification. For example:javascript"overrides": { "Toys": { "perParent": "ToyBox", "context": "[*discriminator=Elements.Toy]", "identity": {...}, "behaviors": {...}, "schema": {...} } },
The value of the perParent property is the parent of the object, i.e.
ToyBox
for Toys
, which tells Hypar what the parent is. This is typically a property containing the element Identifier of a parent element, which must be present on the child element’s schema. In the example above, we are creating an override on
Toy
elements, which each have a ToyBox
property which holds the identifier of a ToyBox
element. The Element schema:
javascript{ "x-namespace": "Elements", "required": [], "properties": { "ToyBox": { "type": [ "string", "null" ], "format": "uuid", "$hyparRelationship": { "type": "child-of", "discriminators": [ "Elements.ToyBox" ] } } }, "additionalProperties": false, "description": "A toy", "allOf": [ { "$ref": "https://schemas.hypar.io/GeometricElement.json" } ] }
The Element class:
c#public partial class Toy : GeometricElement { public Guid? ToyBox { get; set; } }
💡
It is important that the value of the
perParent
property correspond to the name of a property on the element you’re overriding, and that that property be marked with $hyparRelationship
in its schema.In the UI, when a user overrides the child Element, the child Element’s parent is used to limit which Elements are editable in that operation. If the user clicks the override button directly, a dropdown menu displays to allow the user to pick which parent’s Elements they wish to edit. If the user double clicks or preselects an Element, then the parent of the child Element is automatically selected.
Geometric Parents
If the parent element inherits from
Element
, and has no geometry in the scene, it will behave as shown in the selection example immediately above. If it inherits from GeometricElement
, then the user will be able to pick the parent geometry directly instead of choosing from a dropdown. Here, the ToyBox
element type is changed to be a GeometricElement
: View-associated Parents
An Element type can be associated with a programmatically created View that will become the default View for editing the Element’s children.
The associated View must be represented as a
$hyparRelationship
in the Element’s schema, as in the following example for the ToyBox
Element schema.json{ "x-namespace": "Elements", "required": [], "properties": { "Toy Box View": { "$hyparRelationship": { "type": "has-view", "discriminator": "Elements.ViewScope" }, "description": "The default view for this toy box", "type": [ "string", "null" ], "format": "uuid" } }, "additionalProperties": false, "description": "A box containing toys", "allOf": [ { "$ref": "https://schemas.hypar.io/GeometricElement.json" } ] }
💡
Assuming a
Toy
Element has an override perParent
property of ToyBox
, creating a new Toy
Element within a ToyBox
’s programmatically created View automatically assigns the associated ToyBox
as the Toy
's parent.Adding Elements to Parents with $hyparRelation
If your child Element override supports the
add
behavior, you will need to associate the added child Element with a parent using the override schema in hypar.json
:javascript"overrides": { "Toys": { "perParent": "ToyBox", "context": "[*discriminator=Elements.Toy]", "identity": {...}, "behaviors": {...}, "schema": {...} } },
- Within the Element’s
“overrides”
set"add": true
in the“behaviors”
section.
- To associate the child Element with its parent, we’ll build out a schema definition for the
"perParent"
property identifying"ToyBox"
.
- We mark the new
“ToyBox”
entry with$hyparRelation
to associate the child’s override with its parent.
- The
$hyparRelation
entry designates the properties needed to uniquely identify the parent Element, in this case by the“Name”
value.
javascript"overrides": { "Toys": { "perParent": "ToyBox", "context": "[*discriminator=Elements.Toy]", "identity": { "Name": { "type": "string" } }, "behaviors": { "add": true }, "schema": { "ToyBox": { "$hyparRelation": { "$ref": "https://schemas.hypar.io/ToyBox.json" }, "type": "object", "properties": { "Name": { "type": "string" } } }, "Transform": { "$ref": "https://schemas.hypar.io/Transform.json" } } } },
⚠️
Relying on the Element
Id
property as the identifier is not safe here as the Id
will change every time the Function re-executes. To uniquely identify an Element instance, use a property that stays constant across Function executions.The override logic in the associated Function must access the parent property on each overridden Element to identify the correct parent to set for the Element.
Here is a full function logic example:
c#public static PerParentExample2Outputs Execute(Dictionary<string, Model> inputModels, PerParentExample2Inputs input) { // Set up Parent Elements var toyBox1 = new ToyBox { Name = "Box 1" }; toyBox1.Representation = new Lamina(Polygon.Rectangle((-1, -1, 0), (5, 1, 0))); toyBox1.Material = BuiltInMaterials.XAxis; var toyBox2 = new ToyBox { Name = "Box 2" }; toyBox2.Representation = new Lamina(Polygon.Rectangle((-1, 1, 0), (5, 3, 0))); toyBox2.Material = BuiltInMaterials.YAxis; // Set up views for parent elements var toybox1View = new ViewScope() { Name = "Box 1 View", Camera = new Camera { NamedPosition = CameraNamedPosition.Top }, ClipWithBoundingBox = false, BoundingBox = new BBox3(toyBox1) }; toybox1View.BoundingBox.Extend((0, 0, 2)); toyBox1.ToyBoxView = toybox1View.Id; var toybox2View = new ViewScope() { Name = "Box 2 View", Camera = new Camera { NamedPosition = CameraNamedPosition.Top }, ClipWithBoundingBox = false, BoundingBox = new BBox3(toyBox2) }; toybox2View.BoundingBox.Extend((0, 0, 2)); toyBox2.ToyBoxView = toybox2View.Id; // Create default child elements var toy1 = new Toy { Transform = new Transform(0, 0, 0), ToyBox = toyBox1.Id, Name = "Toy 1" }; var toy2 = new Toy { Transform = new Transform(2, 0, 0), ToyBox = toyBox1.Id, Name = "Toy 2" }; var toy3 = new Toy { Transform = new Transform(4, 0, 0), ToyBox = toyBox1.Id, Name = "Toy 3" }; var toy4 = new Toy { Transform = new Transform(0, 2, 0), ToyBox = toyBox2.Id, Name = "Toy 4" }; var toy5 = new Toy { Transform = new Transform(2, 2, 0), ToyBox = toyBox2.Id, Name = "Toy 5" }; var toy6 = new Toy { Transform = new Transform(4, 2, 0), ToyBox = toyBox2.Id, Name = "Toy 6" }; var output = new PerParentExample2Outputs(); var toys = new[] { toy1, toy2, toy3, toy4, toy5, toy6 }; // Implement Overrides for child elements, choosing the correct parent ID based on the override value var toysOverridden = input.Overrides.Toys.CreateElements( input.Overrides.Additions.Toys, (add) => new Toy { Transform = add.Value.Transform, ToyBox = add.Value.ToyBox.Name == "Box 1" ? toyBox1.Id : toyBox2.Id }, (elem, identity) => identity.Name == elem.Name, (elem, edit) => { elem.Transform = edit.Value.Transform; elem.ToyBox = edit.Value.ToyBox.Name == "Box 1" ? toyBox1.Id : toyBox2.Id; return elem; }, toys); // Add elements to the model output.Model.AddElements( toyBox1, toyBox2, toybox1View, toybox2View); output.Model.AddElements(toysOverridden); return output; }
And the final
hypar.json
:json{ "$schema": "https://hypar.io/Schemas/Function.json", "id": "31cb36d3-4a09-4cc4-b6fc-c5f9eb954e3d", "name": "Per-Parent example 2", "description": "The PerParentExample2 function.", "language": "C#", "model_output": "Toys and Toyboxes", "overrides": { "Toys": { "perParent": "ToyBox", "context": "[*discriminator=Elements.Toy]", "identity": { "Name": { "type": "string" } }, "behaviors": { "add": true }, "schema": { "ToyBox": { "$hyparRelation": { "$ref": "https://schemas.hypar.io/ToyBox.json" }, "type": "object", "properties": { "Name": { "type": "string" } } }, "Transform": { "$ref": "https://schemas.hypar.io/Transform.json" } } } }, "outputs": [ { "unit_type": "volume", "name": "Volume", "description": "The volume.", "type": "number" } ], "repository_url": "https://github.com/hypar-io/function", "last_updated": "0001-01-01T00:00:00", "cli_version": "1.11.0-alpha.18+3cb547a56ea838694dc824edc452e142fe2a45f5", "element_types": [ "https://schemas.hypar.io/ToyBox.json", "https://schemas.hypar.io/Toy.json", "https://schemas.hypar.io/ViewScope.json" ] }
And the final behavior, supporting adding Elements to different parents:
For a complete example implementation, see github.com/hypar-io/function-perParentExample/.