Add/Remove Overrides

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:
"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:
"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:
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:
// 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.
// 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.
// 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:
"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:
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:
// 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:
// 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:
// 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 an edit override in hypar.json.
  • These behaviors will codegen to new Additions and Removals objects on your Input.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.
      ย