What are Add/Remove Overrides?
For a given
paradigm: edit
override, it is possible to introduce additional behaviors
that allow users to add and delete the elements targeted by that override.Consider a function that makes Walls, each based on a line that the user draws. To support editing the line for each wall, we would specify an edit override like this:
json"Walls": { "context": "[*discriminator=Elements.StandardWall]", "identity": { "Rough Location": { "$ref": "https://hypar.io/Schemas/Geometry/Vector3.json" } }, "schema": { "CenterLine": { "$ref": "https://prod-api.hypar.io/schemas/Line" } } }
We're using a rough physical location (perhaps the midpoint of the line where it was originally drawn) as our
identity
, and using the centerline of the wall as the editable geometric property we want to override (the schema
).Addition
Next, we want to allow users to add new walls at arbitrary locations, in addition to any auto-generated walls. We can add a
behavior
to this override to tell it to support adding new walls:json"Walls": { "context": "[*discriminator=Elements.StandardWall]", "identity": { "Rough Location": { "$ref": "https://hypar.io/Schemas/Geometry/Vector3.json" } }, "schema": { "CenterLine": { "$ref": "https://prod-api.hypar.io/schemas/Line" } }, "behaviors": { "add": { "schema": { "CenterLine": { "$ref": "https://prod-api.hypar.io/schemas/Line" } } } } }
An "add" behavior takes a schema, just like the edit override does. This schema can be the same as the one utilized by the override it belongs to, but it doesn't have to be.
When we run
hypar init
, new code will be generated. This will generate a new property in the
Overrides
class that's created. That class will have a WallsOverride
object called Walls
, but now it will also have an OverrideAdditions
property called Additions
. The generated class will look something like this:c#public partial class Overrides { public Overrides(OverrideAdditions @additions, OverrideRemovals @removals, IList<WallsOverride> @walls, IList<WallPropertiesOverride> @wallProperties) { this.Additions = @additions; this.Walls = @walls; this.WallProperties = @wallProperties; } public OverrideAdditions Additions { get; set; } public IList<WallsOverride> Walls { get; set; } }
The
Additions
property is added because of the add
behavior we added to our override. There will be a property in OverrideAdditions
called Walls
containing the newly added walls we need to handle.In our function code, now we can look at those values, and use them to construct new walls:
c#// for every addition foreach (var newWall in input.Overrides.Additions.Walls) { var wallLine = newWall.Value.CenterLine; var wallThickness = 0.15; var wallheight = 3.0; // create wall var wall = new StandardWall(wallLine, wallThickness, wallheight, defaultWallMaterial); // add wall to model output.Model.AddElement(wall); }
We also want to make sure we attach the relevant Identity information, so that when we go to edit this wall, we know we're editing the right wall.
c#// for every addition foreach (var newWall in input.Overrides.Additions.Walls) { var wallLine = newWall.Value.CenterLine; var wallCenter = wallLine.PointAt(0.5); var wallThickness = 0.15; var wallheight = 3.0; // create wall var wall = new StandardWall(wallLine, wallThickness, wallheight, defaultWallMaterial); // attach identity information and associated overrides wall.AdditionalProperties["Rough Location"] = wallCenter; Identity.AddOverrideIdentity(wall, newWall); // add wall to model output.Model.AddElement(wall); }
Next, we'll need to handle our
edit
overrides. It's often convenient to do this at the same time as new elements are being created. We use the identity to match the edit override with the corresponding add override. If there is a matching edit override, we use its centerline, instead of the one associated with the original add. We must also attach identity information from this override to the created wall as well.From a user's standpoint, this will feel straightforward: first they draw a line, then they select a wall and edit it, and see their changes applied.
c#// for every addition foreach (var newWall in input.Overrides.Additions.Walls) { var wallLine = newWall.Value.CenterLine; var wallCenter = wallLine.PointAt(0.5); // get matching edit overrides if (input.Overrides?.Walls != null) { var matchingEditOverride = input.Overrides.Walls.FirstOrDefault((w) => w.Identity.RoughLocation.DistanceTo(wallCenter) < 0.1); if (matchingEditOverride != null) { wallLine = matchingEditOverride.Value.CenterLine; } var wallThickness = 0.15; var wallheight = 3.0; // create wall var wall = new StandardWall(wallLine, wallThickness, wallheight, defaultWallMaterial); // attach identity information and associated overrides wall.AdditionalProperties["Rough Location"] = wallCenter; Identity.AddOverrideIdentity(wall, newWall); // also attach identity information from the matching edit override, // if present. if (matchingOverride != null) { Identity.AddOverrideIdentity(wall, matchingEditOverride) } // add wall to model output.Model.AddElement(wall); }
If we were also generating walls automatically in our function, we'd also have to look for matching edit overrides for those walls not produced as a consequence of an add override.
Removal
Removing an element is much easier than adding it. To enable removal, modify the
behaviors
of your override like so:json"Walls": { "context": "[*discriminator=Elements.StandardWall]", "identity": { "Rough Location": { "$ref": "https://hypar.io/Schemas/Geometry/Vector3.json" } }, "schema": { "CenterLine": { "$ref": "https://prod-api.hypar.io/schemas/Line" } }, "behaviors": { "add": { "schema": { "CenterLine": { "$ref": "https://prod-api.hypar.io/schemas/Line" } } }, "remove": true } }
If an element was created via an add override, then removal requires no additional effort β in the UI, when a user removes an element created via an add override, it simply deletes the add override, and any associated edit overrides.
If the only way your function creates elements is via add overrides, then all you need to do to support removal is set
"remove": "true"
in behaviors
. Note that this depends on having correctly associated the add override's identity with the element.
(Identity.AddOverrideIdentity(wall, newWall)
above.)If an element was created "automatically" β that is, not via an add override β then we will receive a new "removal override" which we need to handle in our code.
The generated
Overrides
class will now look like this:c#public partial class Overrides { public Overrides(OverrideAdditions @additions, OverrideRemovals @removals, IList<WallsOverride> @walls, IList<WallPropertiesOverride> @wallProperties) { this.Additions = @additions; this.Removals = @removals; this.Walls = @walls; this.WallProperties = @wallProperties; } public OverrideAdditions Additions { get; set; } public OverrideRemovals Removals { get; set; } public IList<WallsOverride> Walls { get; set; } }
Just like with additions, there will be a property on
OverrideRemovals
called Walls
, and this will contain the necessary information about which walls have been manually deleted by the user.If our original code for generating walls automatically looked like this:
c#// create walls automatically var rectangle = Polygon.Rectangle(10, 10); foreach (var seg in rectangle.Segments()) { var wall = new StandardWall(seg, 0.15, 3.0, defaultWallMaterial); output.Model.AddElement(wall); }
We'd need to make sure we handled any edit overrides here too:
c#// create walls automatically var rectangle = Polygon.Rectangle(10, 10); foreach (var seg in rectangle.Segments()) { var wallLine = seg; var wallCenter = seg.PointAt(0.5); // get matching edit overrides var matchingEditOverride = input.Overrides.Walls.FirstOrDefault((w) => w.Identity.RoughLocation.DistanceTo(wallCenter) < 0.1); if (matchingEditOverride != null) { wallLine = matchingEditOverride.Value.CenterLine; } // create wall var wall = new StandardWall(wallLine, 0.15, 3.0, defaultWallMaterial); output.Model.AddElement(wall); }
And finally, we'll need to handle any remove overrides, to make sure the wall in question never gets created if it was explicitly removed by a user:
c#// create walls automatically var rectangle = Polygon.Rectangle(10, 10); foreach (var seg in rectangle.Segments()) { var wallLine = seg; var wallCenter = seg.PointAt(0.5); // get matching remove overrides if (input.Overrides?.Removals?.Walls != null) { var matchingRemoval = input.Overrides.Removals.Walls .FirstOrDefault(r => r.Identity.RoughLocation.DistanceTo(wallCenter) < 0.01); if (matchingRemoval != null) { // skip creating this wall altogether continue; } } // get matching edit overrides var matchingEditOverride = input.Overrides.Walls.FirstOrDefault((w) => w.Identity.RoughLocation.DistanceTo(wallCenter) < 0.1); if (matchingEditOverride != null) { wallLine = matchingEditOverride.Value.CenterLine; } // create wall var wall = new StandardWall(wallLine, 0.15, 3.0, defaultWallMaterial); output.Model.AddElement(wall); }
Summary
- Support adding/removing elements by adding
behaviors
to the configuration of anedit
override inhypar.json
.
- These behaviors will codegen to new
Additions
andRemovals
objects on yourInput.Overrides
object.
- In your function code, for a full implementation of Add/Edit/Remove behaviors, you must make sure to:
- create new elements from any Additions
- attach the identity from each addition to the element it caused to be created via
Identity.AddOverrideIdentity
. - apply any edit overrides to elements created in your function, whether or not they are generated from an add override.
- For each edit override that modifies an element, you must also attach the override's identity to that element via
Identity.AddOverrideIdentity
. - If your function creates elements by default (without any add overrides), you must also support removing any elements that match the identity of any
Removal
override. You can do this by preventing the element from being created in the first place, or removing it from the model after creation and before return.
See the Walls function in Building Blocks for a complete example of Add/Remove overrides.