diff --git a/WorkplaceStrategy/SpaceMetrics/.gitignore b/WorkplaceStrategy/SpaceMetrics/.gitignore
new file mode 100644
index 00000000..f1d4f671
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/.gitignore
@@ -0,0 +1,9 @@
+
+bin/
+obj/
+*.glb
+output.json
+input.json
+.vs/
+server/
+test/Generated/
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/README.md b/WorkplaceStrategy/SpaceMetrics/README.md
new file mode 100644
index 00000000..9857fb16
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/README.md
@@ -0,0 +1,19 @@
+
+
+# Space Metrics
+
+Override various workplace metrics from a layout.
+
+|Input Name|Type|Description|
+|---|---|---|
+
+
+
+
+|Output Name|Type|Description|
+|---|---|---|
+
+
+
+
+## Additional Information
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/SpaceMetrics.sln b/WorkplaceStrategy/SpaceMetrics/SpaceMetrics.sln
new file mode 100644
index 00000000..105301a0
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/SpaceMetrics.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceMetrics", "src\SpaceMetrics.csproj", "{6FF5C3A5-79FC-426A-9F58-25DF57530B78}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceMetrics.Dependencies", "dependencies\SpaceMetrics.Dependencies.csproj", "{CDB3F17C-D096-4BEB-94D8-BFDB3E1888F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpaceMetrics.Tests", "test\SpaceMetrics.Tests.csproj", "{360608A1-942E-4751-B878-E5239BF0595D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6FF5C3A5-79FC-426A-9F58-25DF57530B78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6FF5C3A5-79FC-426A-9F58-25DF57530B78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6FF5C3A5-79FC-426A-9F58-25DF57530B78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6FF5C3A5-79FC-426A-9F58-25DF57530B78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CDB3F17C-D096-4BEB-94D8-BFDB3E1888F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CDB3F17C-D096-4BEB-94D8-BFDB3E1888F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CDB3F17C-D096-4BEB-94D8-BFDB3E1888F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CDB3F17C-D096-4BEB-94D8-BFDB3E1888F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {360608A1-942E-4751-B878-E5239BF0595D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {360608A1-942E-4751-B878-E5239BF0595D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {360608A1-942E-4751-B878-E5239BF0595D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {360608A1-942E-4751-B878-E5239BF0595D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceBoundary.cs b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceBoundary.cs
new file mode 100644
index 00000000..ed41d5d2
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceBoundary.cs
@@ -0,0 +1,8 @@
+using Elements.Geometry;
+namespace Elements
+{
+ public partial class SpaceBoundary
+ {
+ public Vector3? ParentCentroid { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceBoundary.g.cs b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceBoundary.g.cs
new file mode 100644
index 00000000..8ce5974d
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceBoundary.g.cs
@@ -0,0 +1,107 @@
+//----------------------
+//
+// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org)
+//
+//----------------------
+using Elements;
+using Elements.GeoJSON;
+using Elements.Geometry;
+using Elements.Geometry.Solids;
+using Elements.Spatial;
+using Elements.Validators;
+using Elements.Serialization.JSON;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Line = Elements.Geometry.Line;
+using Polygon = Elements.Geometry.Polygon;
+
+namespace Elements
+{
+ #pragma warning disable // Disable all warnings
+
+ /// A profile with a program assigned to it, and optional internal cell geometry.
+ [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")]
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class SpaceBoundary : GeometricElement
+ {
+ [JsonConstructor]
+ public SpaceBoundary(Profile @boundary, IList @cells, double @area, double? @length, double? @depth, double @height, string @programGroup, string @programType, System.Guid? @programRequirement, System.Guid? @level, System.Guid? @levelLayout, string @hyparSpaceType, string @defaultWallType, Transform @transform = null, Material @material = null, Representation @representation = null, bool @isElementDefinition = false, System.Guid @id = default, string @name = null)
+ : base(transform, material, representation, isElementDefinition, id, name)
+ {
+ this.Boundary = @boundary;
+ this.Cells = @cells;
+ this.Area = @area;
+ this.Length = @length;
+ this.Depth = @depth;
+ this.Height = @height;
+ this.ProgramGroup = @programGroup;
+ this.ProgramType = @programType;
+ this.ProgramRequirement = @programRequirement;
+ this.Level = @level;
+ this.LevelLayout = @levelLayout;
+ this.HyparSpaceType = @hyparSpaceType;
+ this.DefaultWallType = @defaultWallType;
+ }
+
+
+ // Empty constructor
+ public SpaceBoundary()
+ : base()
+ {
+ }
+
+ /// The boundary of the space
+ [JsonProperty("Boundary", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Profile Boundary { get; set; }
+
+ /// Component cells making up the boundary
+ [JsonProperty("Cells", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public IList Cells { get; set; }
+
+ /// The area of the boundary
+ [JsonProperty("Area", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Area { get; set; }
+
+ /// The rough length of this space boundary, parallel to the accessible edge
+ [JsonProperty("Length", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double? Length { get; set; }
+
+ /// The rough depth of the space boundary, perpendicular to the accessible edge
+ [JsonProperty("Depth", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double? Depth { get; set; }
+
+ /// The height of this space boundary
+ [JsonProperty("Height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Height { get; set; }
+
+ /// A program grouping, like a department.
+ [JsonProperty("Program Group", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string ProgramGroup { get; set; }
+
+ /// The name of the program type assigned to this space (like "Open Office" or "Meeting Room")
+ [JsonProperty("Program Type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string ProgramType { get; set; }
+
+ [JsonProperty("Program Requirement", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Guid? ProgramRequirement { get; set; }
+
+ [JsonProperty("Level", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Guid? Level { get; set; }
+
+ /// The layout, if any, which generated this space boundary.
+ [JsonProperty("Level Layout", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Guid? LevelLayout { get; set; }
+
+ /// The hypar-recognized space type name which will be used to determine which layout function to apply. In older space boundaries, this may not be set — fall back to the Name property for this purpose if not provided.
+ [JsonProperty("Hypar Space Type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string HyparSpaceType { get; set; }
+
+ /// What wall type should generally be created for this space type? This may get overridden later on for a specific wall.
+ [JsonProperty("Default Wall Type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string DefaultWallType { get; set; }
+
+
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetric.g.cs b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetric.g.cs
new file mode 100644
index 00000000..5b05e4bc
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetric.g.cs
@@ -0,0 +1,69 @@
+//----------------------
+//
+// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org)
+//
+//----------------------
+using Elements;
+using Elements.GeoJSON;
+using Elements.Geometry;
+using Elements.Geometry.Solids;
+using Elements.Spatial;
+using Elements.Validators;
+using Elements.Serialization.JSON;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Line = Elements.Geometry.Line;
+using Polygon = Elements.Geometry.Polygon;
+
+namespace Elements
+{
+ #pragma warning disable // Disable all warnings
+
+ /// Attach these to spaces to include information useful for metrics and calculations
+ [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")]
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class SpaceMetric : Element
+ {
+ [JsonConstructor]
+ public SpaceMetric(System.Guid? @space, double @seats, double @headcount, double @desks, double @collaborationSeats, System.Guid @id = default, string @name = null)
+ : base(id, name)
+ {
+ this.Space = @space;
+ this.Seats = @seats;
+ this.Headcount = @headcount;
+ this.Desks = @desks;
+ this.CollaborationSeats = @collaborationSeats;
+ }
+
+
+ // Empty constructor
+ public SpaceMetric()
+ : base()
+ {
+ }
+
+ /// The space we are attaching metrics to
+ [JsonProperty("Space", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Guid? Space { get; set; }
+
+ /// Seat count for this space.
+ [JsonProperty("Seats", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Seats { get; set; }
+
+ /// Headcount for this space.
+ [JsonProperty("Headcount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Headcount { get; set; }
+
+ /// Desks for this space.
+ [JsonProperty("Desks", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Desks { get; set; }
+
+ /// Seats for collaboration in this space.
+ [JsonProperty("Collaboration Seats", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double CollaborationSeats { get; set; }
+
+
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetrics.Dependencies.csproj b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetrics.Dependencies.csproj
new file mode 100644
index 00000000..69448574
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetrics.Dependencies.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsInputs.g.cs b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsInputs.g.cs
new file mode 100644
index 00000000..f578d6c6
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsInputs.g.cs
@@ -0,0 +1,183 @@
+// This code was generated by Hypar.
+// Edits to this code will be overwritten the next time you run 'hypar init'.
+// DO NOT EDIT THIS FILE.
+
+using Elements;
+using Elements.GeoJSON;
+using Elements.Geometry;
+using Elements.Geometry.Solids;
+using Elements.Validators;
+using Elements.Serialization.JSON;
+using Hypar.Functions;
+using Hypar.Functions.Execution;
+using Hypar.Functions.Execution.AWS;
+using Hypar.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Line = Elements.Geometry.Line;
+using Polygon = Elements.Geometry.Polygon;
+
+namespace SpaceMetrics
+{
+ #pragma warning disable // Disable all warnings
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+
+ public class SpaceMetricsInputs : ArgsBase
+
+ {
+ [Newtonsoft.Json.JsonConstructor]
+
+ public SpaceMetricsInputs(Overrides @overrides, Dictionary modelInputKeys, string gltfKey, string elementsKey, string ifcKey):
+ base(modelInputKeys, gltfKey, elementsKey, ifcKey)
+ {
+ var validator = Validator.Instance.GetFirstValidatorForType();
+ if(validator != null)
+ {
+ validator.PreConstruct(new object[]{ @overrides});
+ }
+
+ this.Overrides = @overrides ?? this.Overrides;
+
+ if(validator != null)
+ {
+ validator.PostConstruct(this);
+ }
+ }
+
+ [Newtonsoft.Json.JsonProperty("overrides", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Overrides Overrides { get; set; } = new Overrides();
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+
+ public partial class Overrides
+
+ {
+ public Overrides() { }
+
+ [Newtonsoft.Json.JsonConstructor]
+ public Overrides(IList @spaceMetrics)
+ {
+ var validator = Validator.Instance.GetFirstValidatorForType();
+ if(validator != null)
+ {
+ validator.PreConstruct(new object[]{ @spaceMetrics});
+ }
+
+ this.SpaceMetrics = @spaceMetrics ?? this.SpaceMetrics;
+
+ if(validator != null)
+ {
+ validator.PostConstruct(this);
+ }
+ }
+
+ [Newtonsoft.Json.JsonProperty("Space Metrics", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public IList SpaceMetrics { get; set; } = new List();
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+
+ public partial class SpaceMetricsOverride
+
+ {
+ [Newtonsoft.Json.JsonConstructor]
+ public SpaceMetricsOverride(string @id, SpaceMetricsIdentity @identity, SpaceMetricsValue @value)
+ {
+ var validator = Validator.Instance.GetFirstValidatorForType();
+ if(validator != null)
+ {
+ validator.PreConstruct(new object[]{ @id, @identity, @value});
+ }
+
+ this.Id = @id;
+ this.Identity = @identity;
+ this.Value = @value;
+
+ if(validator != null)
+ {
+ validator.PostConstruct(this);
+ }
+ }
+
+ [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Id { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public SpaceMetricsIdentity Identity { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public SpaceMetricsValue Value { get; set; }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+
+ public partial class SpaceMetricsIdentity
+
+ {
+ [Newtonsoft.Json.JsonConstructor]
+ public SpaceMetricsIdentity(Vector3 @parentCentroid)
+ {
+ var validator = Validator.Instance.GetFirstValidatorForType();
+ if(validator != null)
+ {
+ validator.PreConstruct(new object[]{ @parentCentroid});
+ }
+
+ this.ParentCentroid = @parentCentroid;
+
+ if(validator != null)
+ {
+ validator.PostConstruct(this);
+ }
+ }
+
+ [Newtonsoft.Json.JsonProperty("ParentCentroid", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Vector3 ParentCentroid { get; set; }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")]
+
+ public partial class SpaceMetricsValue
+
+ {
+ [Newtonsoft.Json.JsonConstructor]
+ public SpaceMetricsValue(double @seats, double @headcount, double @desks, double @collaborationSeats)
+ {
+ var validator = Validator.Instance.GetFirstValidatorForType();
+ if(validator != null)
+ {
+ validator.PreConstruct(new object[]{ @seats, @headcount, @desks, @collaborationSeats});
+ }
+
+ this.Seats = @seats;
+ this.Headcount = @headcount;
+ this.Desks = @desks;
+ this.CollaborationSeats = @collaborationSeats;
+
+ if(validator != null)
+ {
+ validator.PostConstruct(this);
+ }
+ }
+
+ [Newtonsoft.Json.JsonProperty("Seats", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Seats { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("Headcount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Headcount { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("Desks", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double Desks { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("Collaboration Seats", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double CollaborationSeats { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsOutputs.g.cs b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsOutputs.g.cs
new file mode 100644
index 00000000..7ac0c500
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsOutputs.g.cs
@@ -0,0 +1,29 @@
+// This code was generated by Hypar.
+// Edits to this code will be overwritten the next time you run 'hypar init'.
+// DO NOT EDIT THIS FILE.
+
+using Elements;
+using Elements.GeoJSON;
+using Elements.Geometry;
+using Hypar.Functions;
+using Hypar.Functions.Execution;
+using Hypar.Functions.Execution.AWS;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System.Collections.Generic;
+
+namespace SpaceMetrics
+{
+ public class SpaceMetricsOutputs: SystemResults
+ {
+
+ ///
+ /// Construct a SpaceMetricsOutputs with default inputs.
+ /// This should be used for testing only.
+ ///
+ public SpaceMetricsOutputs() : base()
+ {
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsOverride.g.cs b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsOverride.g.cs
new file mode 100644
index 00000000..1464e595
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/dependencies/SpaceMetricsOverride.g.cs
@@ -0,0 +1,104 @@
+using Elements;
+using System.Collections.Generic;
+using System;
+using System.Linq;
+
+namespace SpaceMetrics
+{
+ ///
+ /// Override metadata for SpaceMetricsOverride
+ ///
+ public partial class SpaceMetricsOverride : IOverride
+ {
+ public static string Name = "Space Metrics";
+ public static string Dependency = "Space Planning Zones";
+ public static string Context = "[*discriminator=Elements.SpaceBoundary]";
+ public static string Paradigm = "Edit";
+
+ ///
+ /// Get the override name for this override.
+ ///
+ public string GetName() {
+ return Name;
+ }
+
+ public object GetIdentity() {
+
+ return Identity;
+ }
+
+ ///
+ /// Get context elements that are applicable to this override.
+ ///
+ /// Dictionary of input models, or any other kind of dictionary of models.
+ /// List of context elements that match what is defined on the override.
+ public static IEnumerable> ContextProxies(Dictionary models) {
+ return models.AllElementsOfType(Dependency).Proxies(Dependency);
+ }
+ }
+ public static class SpaceMetricsOverrideExtensions
+ {
+ ///
+ /// Apply Space Metrics edit overrides to a collection of existing elements
+ ///
+ /// The Space Metrics Overrides to apply
+ /// A collection of existing elements to which to apply the overrides.
+ /// A function returning a boolean which indicates whether an element is a match for an override's identity.
+ /// A function to modify a matched element, returning the modified element.
+ /// The element type this override applies to. Should match the type(s) in the override's context.
+ /// A collection of elements, including unmodified and modified elements from the supplied collection.
+ public static List Apply(
+ this IList overrideData,
+ IEnumerable existingElements,
+ Func identityMatch,
+ Func modifyElement) where T : Element
+ {
+ var resultElements = new List(existingElements);
+ if (overrideData != null)
+ {
+ foreach (var overrideValue in overrideData)
+ {
+ // Assuming there will only be one match per identity, find the first element that matches.
+ var matchingElement = existingElements.FirstOrDefault(e => identityMatch(e, overrideValue.Identity));
+ // if we found a match,
+ if (matchingElement != null)
+ {
+ // remove the old matching element
+ resultElements.Remove(matchingElement);
+ // apply the modification function to it
+ var modifiedElement = modifyElement(matchingElement, overrideValue);
+ // set the identity
+ Identity.AddOverrideIdentity(modifiedElement, overrideValue);
+ //and re-add it to the collection
+ resultElements.Add(modifiedElement);
+ }
+ }
+ }
+ return resultElements;
+ }
+
+ ///
+ /// Apply Space Metrics edit overrides to a collection of existing elements
+ ///
+ /// A collection of existing elements to which to apply the overrides.
+ /// The Space Metrics Overrides to apply — typically `input.Overrides.SpaceMetrics`
+ /// A function returning a boolean which indicates whether an element is a match for an override's identity.
+ /// A function to modify a matched element, returning the modified element.
+ /// The element type this override applies to. Should match the type(s) in the override's context.
+ /// A collection of elements, including unmodified and modified elements from the supplied collection.
+ public static void ApplyOverrides(
+ this List existingElements,
+ IList overrideData,
+ Func identityMatch,
+ Func modifyElement
+ ) where T : Element
+ {
+ var updatedElements = overrideData.Apply(existingElements, identityMatch, modifyElement);
+ existingElements.Clear();
+ existingElements.AddRange(updatedElements);
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/global.json b/WorkplaceStrategy/SpaceMetrics/global.json
new file mode 100644
index 00000000..4aef4472
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/global.json
@@ -0,0 +1,7 @@
+
+{
+ "sdk": {
+ "version": "6.0.400",
+ "rollForward": "latestMinor"
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/hypar.json b/WorkplaceStrategy/SpaceMetrics/hypar.json
new file mode 100644
index 00000000..b94475df
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/hypar.json
@@ -0,0 +1,101 @@
+{
+ "$schema": "https://hypar.io/Schemas/Function.json",
+ "id": "2a34af5b-0d9b-4b11-8a0b-27728a079673",
+ "name": "Space Metrics",
+ "description": "Override various workplace metrics from a layout.",
+ "language": "C#",
+ "model_output": "Space Metrics",
+ "model_dependencies": [
+ {
+ "autohide": false,
+ "name": "Space Planning Zones",
+ "optional": false
+ },
+ {
+ "autohide": false,
+ "name": "Open Office Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Meeting Room Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Classroom Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Phone Booth Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Open Collaboration Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Private Office Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Lounge Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Pantry Layout",
+ "optional": true
+ },
+ {
+ "autohide": false,
+ "name": "Reception Layout",
+ "optional": true
+ }
+ ],
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ }
+ },
+ "overrides": {
+ "Space Metrics": {
+ "$hyparOrder": 1,
+ "dependency": "Space Planning Zones",
+ "context": "[*discriminator=Elements.SpaceBoundary]",
+ "identity": {
+ "ParentCentroid": {
+ "$ref": "https://hypar.io/Schemas/Geometry/Vector3.json"
+ }
+ },
+ "paradigm": "edit",
+ "schema": {
+ "Seats": {
+ "type": "number"
+ },
+ "Headcount": {
+ "type": "number"
+ },
+ "Desks": {
+ "type": "number"
+ },
+ "Collaboration Seats": {
+ "type": "number"
+ }
+ }
+ }
+ },
+ "outputs": [],
+ "element_types": [
+ "https://schemas.hypar.io/SpaceMetric.json",
+ "https://schemas.hypar.io/SpaceBoundary.json"
+ ],
+ "repository_url": "",
+ "filters": {},
+ "last_updated": "0001-01-01T00:00:00",
+ "cli_version": "1.10.0"
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/src/Function.g.cs b/WorkplaceStrategy/SpaceMetrics/src/Function.g.cs
new file mode 100644
index 00000000..64eabb32
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/src/Function.g.cs
@@ -0,0 +1,73 @@
+// This code was generated by Hypar.
+// Edits to this code will be overwritten the next time you run 'hypar init'.
+// DO NOT EDIT THIS FILE.
+
+using Amazon.Lambda.Core;
+using Hypar.Functions.Execution;
+using Hypar.Functions.Execution.AWS;
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
+namespace SpaceMetrics
+{
+ public class Function
+ {
+ // Cache the model store for use by subsequent
+ // executions of this lambda.
+ private UrlModelStore store;
+
+ public async Task Handler(SpaceMetricsInputs args)
+ {
+ // Preload dependencies (if they exist),
+ // so that they are available during model deserialization.
+
+ var sw = System.Diagnostics.Stopwatch.StartNew();
+ var asmLocation = this.GetType().Assembly.Location;
+ var asmDir = Path.GetDirectoryName(asmLocation);
+
+ // Explicitly load the dependencies project, it might have types
+ // that aren't used in the function but are necessary for correct
+ // deserialization.
+ var asmName = Path.GetFileNameWithoutExtension(asmLocation);
+ var depPath = Path.Combine(asmDir, $"{asmName}.Dependencies.dll");
+ if(File.Exists(depPath))
+ {
+ Console.WriteLine($"Loading dependencies assembly from: {depPath}...");
+ Assembly.LoadFrom(depPath);
+ Console.WriteLine("Dependencies assembly loaded.");
+ }
+
+ // Load all reference assemblies.
+ Console.WriteLine($"Loading all referenced assemblies.");
+ foreach (var asm in this.GetType().Assembly.GetReferencedAssemblies())
+ {
+ try
+ {
+ Console.WriteLine($"Assembly Name: {asm.FullName}");
+ Assembly.Load(asm);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Failed to load {asm.FullName}");
+ Console.WriteLine(e.Message);
+ }
+ }
+ sw.Stop();
+ Console.WriteLine($"Time to load assemblies: {sw.Elapsed.TotalSeconds})");
+
+ if(this.store == null)
+ {
+ this.store = new UrlModelStore();
+ }
+
+
+ var l = new InvocationWrapper (store, SpaceMetrics.Execute);
+ var output = await l.InvokeAsync(args);
+ return output;
+ }
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/src/SpaceMetrics.cs b/WorkplaceStrategy/SpaceMetrics/src/SpaceMetrics.cs
new file mode 100644
index 00000000..89d17f29
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/src/SpaceMetrics.cs
@@ -0,0 +1,146 @@
+using Elements;
+using Elements.Geometry;
+using System.Collections.Generic;
+
+namespace SpaceMetrics
+{
+ public static class SpaceMetrics
+ {
+ private static readonly List> proxies = new List>();
+ private static readonly string SpaceMetricDependencyName = SpaceMetricsOverride.Dependency;
+
+ private const string _openOffice = "Open Office";
+ private const string _openCollab = "Open Collaboration";
+
+ ///
+ ///
+ ///
+ /// The input model.
+ /// The arguments to the execution.
+ /// A SpaceMetricsOutputs instance containing computed results and the model with any new elements.
+ public static SpaceMetricsOutputs Execute(Dictionary inputModels, SpaceMetricsInputs input)
+ {
+ proxies.Clear();
+ var output = new SpaceMetricsOutputs();
+
+ var spaceMetrics = new List();
+ if (inputModels.TryGetValue("Space Planning Zones", out var zonesModel))
+ {
+ inputModels.TryGetValue(_openOffice + " Layout", out var openOfficeModel);
+ inputModels.TryGetValue(_openCollab + " Layout", out var openCollabModel);
+
+ var allSpaceBoundaries = zonesModel?.AllElementsAssignableFromType().ToList();
+ var openOfficeBoundaries = openOfficeModel?.AllElementsAssignableFromType().ToList();
+ var openCollabSpaceMetrics = openCollabModel?.AllElementsAssignableFromType().ToList();
+
+ var layoutNames = new string[] { _openOffice, _openCollab, "Meeting Room", "Classroom", "Phone Booth", "Private Office", "Lounge", "Reception", "Pantry" };
+ foreach (var layoutName in layoutNames)
+ {
+ spaceMetrics.AddRange(UpdateSpaceMetricsByLayoutType(inputModels, input.Overrides.SpaceMetrics.ToList(), layoutName, allSpaceBoundaries, openOfficeBoundaries, openCollabSpaceMetrics));
+ }
+ }
+
+ output.Model.AddElements(proxies);
+ output.Model.AddElements(spaceMetrics);
+ return output;
+ }
+
+ private static List UpdateSpaceMetricsByLayoutType(
+ Dictionary inputModels,
+ List overrides,
+ string layoutName,
+ List boundaries,
+ List openOfficeBoundaries,
+ List openCollabSpaceMetrics)
+ {
+ var spaceMetrics = new List();
+ if (!inputModels.TryGetValue(layoutName + " Layout", out var layoutModel))
+ {
+ return spaceMetrics;
+ }
+
+ foreach (var sm in layoutModel.AllElementsOfType())
+ {
+ var room = boundaries.FirstOrDefault(b => b.Id == sm.Space);
+ if (room == null)
+ {
+ continue;
+ }
+
+ if (layoutName == _openOffice && openOfficeBoundaries != null && openCollabSpaceMetrics != null)
+ {
+ var openCollabBoundaries = openOfficeBoundaries.Where(b => room.Boundary.Perimeter.Contains(b.Boundary.Perimeter.Centroid()));
+ foreach (var openCollabBoundary in openCollabBoundaries)
+ {
+ var openCollabSM = openCollabSpaceMetrics.FirstOrDefault(osm => osm.Space == openCollabBoundary.Id);
+ sm.Seats += openCollabSM.Seats;
+ sm.Headcount += openCollabSM.Headcount;
+ sm.Desks += openCollabSM.Desks;
+ sm.CollaborationSeats += openCollabSM.CollaborationSeats;
+ }
+ }
+
+ var proxy = GetElementProxy(room, boundaries.Proxies(SpaceMetricDependencyName));
+ var config = MatchApplicableOverride(overrides, proxy, sm);
+ spaceMetrics.Add(new SpaceMetric(room.Id, config.Value.Seats, config.Value.Headcount, config.Value.Desks, config.Value.CollaborationSeats));
+ }
+
+ return spaceMetrics;
+ }
+
+ private static SpaceMetricsOverride MatchApplicableOverride(
+ List overridesById,
+ ElementProxy boundaryProxy,
+ SpaceMetric defaultMetric = null)
+ {
+ var overrideName = SpaceMetricsOverride.Name;
+ SpaceMetricsOverride config = null;
+
+ // See if we already have matching override attached
+ var existingOverrideId = boundaryProxy.OverrideIds(overrideName).FirstOrDefault();
+ if (existingOverrideId != null)
+ {
+ config = overridesById.Find(o => o.Id == existingOverrideId);
+ if (config != null)
+ {
+ return config;
+ }
+ }
+
+ // Try to match from identity in configs
+ config ??= overridesById.Find(o => o.Identity.ParentCentroid.IsAlmostEqualTo(boundaryProxy.Element.ParentCentroid.Value));
+
+ // Use a default in case none found
+ if (config == null)
+ {
+ config = new SpaceMetricsOverride(
+ Guid.NewGuid().ToString(),
+ new SpaceMetricsIdentity(boundaryProxy.Element.ParentCentroid.Value),
+ new SpaceMetricsValue(
+ defaultMetric?.Seats ?? 0,
+ defaultMetric?.Headcount ?? 0,
+ defaultMetric?.Desks ?? 0,
+ defaultMetric?.CollaborationSeats ?? 0)
+ );
+ overridesById.Add(config);
+ }
+
+ // Attach the identity and values data to the proxy
+ boundaryProxy.AddOverrideIdentity(overrideName, config.Id, config.Identity);
+ boundaryProxy.AddOverrideValue(overrideName, config.Value);
+
+ // Make sure proxies list has the proxy so that it will serialize in the model.
+ if (!proxies.Contains(boundaryProxy))
+ {
+ proxies.Add(boundaryProxy);
+ }
+
+ return config;
+ }
+
+ private static ElementProxy GetElementProxy(SpaceBoundary spaceBoundary, IEnumerable> allSpaceBoundaries)
+ {
+ return allSpaceBoundaries.Proxy(spaceBoundary) ?? spaceBoundary.Proxy(SpaceMetricDependencyName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/src/SpaceMetrics.csproj b/WorkplaceStrategy/SpaceMetrics/src/SpaceMetrics.csproj
new file mode 100644
index 00000000..c0c5fc33
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/src/SpaceMetrics.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/WorkplaceStrategy/SpaceMetrics/test/FunctionTest.g.cs b/WorkplaceStrategy/SpaceMetrics/test/FunctionTest.g.cs
new file mode 100644
index 00000000..849938dc
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/test/FunctionTest.g.cs
@@ -0,0 +1,24 @@
+// This code was generated by Hypar.
+// Edits to this code will be overwritten the next time you run 'hypar init'.
+// DO NOT EDIT THIS FILE.
+
+using Xunit;
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+using System;
+using System.Collections.Generic;
+
+namespace SpaceMetrics.Tests
+{
+ public class FunctionTests
+ {
+ private readonly ITestOutputHelper output;
+
+ public FunctionTests(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+ }
+}
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/test/GlobalUsings.cs b/WorkplaceStrategy/SpaceMetrics/test/GlobalUsings.cs
new file mode 100644
index 00000000..8c927eb7
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/test/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/WorkplaceStrategy/SpaceMetrics/test/SpaceMetrics.Tests.csproj b/WorkplaceStrategy/SpaceMetrics/test/SpaceMetrics.Tests.csproj
new file mode 100644
index 00000000..b6e1b61e
--- /dev/null
+++ b/WorkplaceStrategy/SpaceMetrics/test/SpaceMetrics.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/WorkplaceStrategy/WorkplaceMetrics/dependencies/LevelVolume.g.cs b/WorkplaceStrategy/WorkplaceMetrics/dependencies/LevelVolume.g.cs
index be2fb359..d437122c 100644
--- a/WorkplaceStrategy/WorkplaceMetrics/dependencies/LevelVolume.g.cs
+++ b/WorkplaceStrategy/WorkplaceMetrics/dependencies/LevelVolume.g.cs
@@ -76,7 +76,7 @@ public LevelVolume()
public System.Guid? PlanView { get; set; }
/// Multiple profiles used for a collection of volumes
- [JsonProperty("Profiles", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [JsonProperty("Profiles", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public IList Profiles { get; set; }
diff --git a/WorkplaceStrategy/WorkplaceMetrics/hypar.json b/WorkplaceStrategy/WorkplaceMetrics/hypar.json
index ddea9d7b..9b812306 100644
--- a/WorkplaceStrategy/WorkplaceMetrics/hypar.json
+++ b/WorkplaceStrategy/WorkplaceMetrics/hypar.json
@@ -72,6 +72,10 @@
{
"name": "Circulation",
"optional": true
+ },
+ {
+ "name": "Space Metrics",
+ "optional": true
}
],
"input_schema": {
diff --git a/WorkplaceStrategy/WorkplaceMetrics/src/WorkplaceMetrics.cs b/WorkplaceStrategy/WorkplaceMetrics/src/WorkplaceMetrics.cs
index aaca0e65..7b1ba6da 100644
--- a/WorkplaceStrategy/WorkplaceMetrics/src/WorkplaceMetrics.cs
+++ b/WorkplaceStrategy/WorkplaceMetrics/src/WorkplaceMetrics.cs
@@ -32,8 +32,6 @@ public static WorkplaceMetricsOutputs Execute(Dictionary inputMod
var hasFloors = inputModels.TryGetValue("Floors", out var floorsModel);
var hasMass = inputModels.TryGetValue("Conceptual Mass", out var massModel);
var hasCirculation = inputModels.TryGetValue("Circulation", out var circulationModel);
- inputModels.TryGetValue(_openOffice + " Layout", out var openOfficeModel);
- inputModels.TryGetValue(_openCollab + " Layout", out var openCollabModel);
// Get program requirements
var hasProgramRequirements = inputModels.TryGetValue("Program Requirements", out var programReqsModel);
@@ -92,8 +90,6 @@ public static WorkplaceMetricsOutputs Execute(Dictionary inputMod
outputModel.AddElement(settings);
var allSpaceBoundaries = zonesModel.AllElementsAssignableFromType().ToList();
- var openOfficeBoundaries = openOfficeModel?.AllElementsAssignableFromType().ToList();
- var openCollabSpaceMetrics = openCollabModel?.AllElementsAssignableFromType().ToList();
// convert circulation to space boundaries
if (hasCirculation)
@@ -131,11 +127,19 @@ public static WorkplaceMetricsOutputs Execute(Dictionary inputMod
}
+ inputModels.TryGetValue(_openOffice + " Layout", out var openOfficeModel);
+ inputModels.TryGetValue(_openCollab + " Layout", out var openCollabModel);
+ inputModels.TryGetValue("Space Metrics", out var spaceMetricsModel);
+
+ var spaceMetricOverrides = spaceMetricsModel?.AllElementsOfType().ToList();
+ var openOfficeBoundaries = openOfficeModel?.AllElementsAssignableFromType().ToList();
+ var openCollabSpaceMetrics = openCollabModel?.AllElementsAssignableFromType().ToList();
+
var layoutNames = new string[] { _openOffice, _meetingRoom, _classroom, _phoneBooth, _openCollab, _privateOffice, _lounge, _reception, _pantry };
var metricByLayouts = new Dictionary();
foreach (var layoutName in layoutNames)
{
- metricByLayouts[layoutName] = CountWorkplaceTyped(inputModels, input, layoutName, allSpaceBoundaries, openOfficeBoundaries, openCollabSpaceMetrics);
+ metricByLayouts[layoutName] = CountWorkplaceTyped(inputModels, input, layoutName, allSpaceBoundaries, openOfficeBoundaries, openCollabSpaceMetrics, spaceMetricOverrides);
}
var meetingRoomCount = allSpaceBoundaries.Count(sb => sb.Name == "Meeting Room");
@@ -216,7 +220,8 @@ private static SpaceMetric CountWorkplaceTyped(
string layoutName,
List boundaries,
List openOfficeBoundaries,
- List openCollabSpaceMetrics)
+ List openCollabSpaceMetrics,
+ List spaceMetricOverrides)
{
var metric = new SpaceMetric();
if (inputModels.TryGetValue(layoutName + " Layout", out var layoutModel))
@@ -226,7 +231,15 @@ private static SpaceMetric CountWorkplaceTyped(
var room = boundaries.FirstOrDefault(b => b.Id == sm.Space);
if (room != null)
{
- if (layoutName == _openOffice && openOfficeBoundaries != null && openCollabSpaceMetrics != null)
+ var smOverride = spaceMetricOverrides?.FirstOrDefault(o => o.Space == sm.Space);
+ if (smOverride != null)
+ {
+ sm.Seats = smOverride.Seats;
+ sm.Headcount = smOverride.Headcount;
+ sm.Desks = smOverride.Desks;
+ sm.CollaborationSeats = smOverride.CollaborationSeats;
+ }
+ else if (layoutName == _openOffice && openOfficeBoundaries != null && openCollabSpaceMetrics != null)
{
var openCollabBoundaries = openOfficeBoundaries.Where(b => room.Boundary.Perimeter.Contains(b.Boundary.Perimeter.Centroid()));
foreach (var openCollabBoundary in openCollabBoundaries)