diff --git a/LayoutFunctions/ClassroomLayout/src/ClassroomLayout.cs b/LayoutFunctions/ClassroomLayout/src/ClassroomLayout.cs index 7452d23e..11c00e95 100644 --- a/LayoutFunctions/ClassroomLayout/src/ClassroomLayout.cs +++ b/LayoutFunctions/ClassroomLayout/src/ClassroomLayout.cs @@ -72,7 +72,7 @@ public static ClassroomLayoutOutputs Execute(Dictionary inputMode { var seatsCount = 0; var spaceBoundary = room.Boundary; - var levelInvertedTransform = levelVolume.Transform.Inverted(); + var levelInvertedTransform = levelVolume?.Transform.Inverted() ?? new Transform(); var roomWallCandidatesLines = WallGeneration.FindWallCandidates(room, levelVolume?.Profile, corridorSegments, out Line orientationGuideEdge) .Select(c => (c.line.TransformedLine(levelInvertedTransform), c.type)); wallCandidateLines.AddRange(roomWallCandidatesLines); diff --git a/LayoutFunctions/LayoutFunctionCommon/LayoutGeneration.cs b/LayoutFunctions/LayoutFunctionCommon/LayoutGeneration.cs new file mode 100644 index 00000000..03125d03 --- /dev/null +++ b/LayoutFunctions/LayoutFunctionCommon/LayoutGeneration.cs @@ -0,0 +1,306 @@ +using Elements; +using Elements.Components; +using Elements.Geometry; +using Elements.Spatial; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace LayoutFunctionCommon +{ + public class LayoutGenerationResult + { + public Model OutputModel { get; set; } + public int SeatsCount { get; set; } + } + + public record struct ConfigInfo(string ConfigName, ContentConfiguration Config, Polygon Rectangle); + + public record struct SeatsCount(int Seats, int Headcount, int Desks, int CollaborationSeats); + + public class LayoutGeneration + where TLevelElements : Element, ILevelElements + where TSpaceBoundary : ISpaceBoundary + where TLevelVolume : GeometricElement, ILevelVolume + where TCirculationSegment : Floor, ICirculationSegment + { + public virtual LayoutGenerationResult StandardLayoutOnAllLevels(string programTypeName, + Dictionary inputModels, + dynamic overrides, + bool createWalls, + string configurationsPath, + string catalogPath = "catalog.json") + { + + var outputModel = new Model(); + var totalSeats = 0; + ContentCatalogRetrieval.SetCatalogFilePath(catalogPath); + var spacePlanningZones = inputModels["Space Planning Zones"]; + var levels = GetLevels(inputModels, spacePlanningZones); + var levelVolumes = LayoutStrategies.GetLevelVolumes(inputModels); + var configJson = configurationsPath != null ? File.ReadAllText(configurationsPath) : "{}"; + var configs = DeserializeConfigJson(configJson); + foreach (var lvl in levels) + { + var corridors = lvl.Elements.OfType(); + var corridorSegments = corridors.SelectMany(p => p.Profile.Segments()); + var roomBoundaries = lvl.Elements.OfType().Where(z => z.Name == programTypeName); + var levelVolume = levelVolumes.FirstOrDefault(l => + (lvl.AdditionalProperties.TryGetValue("LevelVolumeId", out var levelVolumeId) && + levelVolumeId as string == l.Id.ToString())) ?? + levelVolumes.FirstOrDefault(l => l.Name == lvl.Name); + var wallCandidateLines = new List<(Line line, string type)>(); + foreach (var room in roomBoundaries) + { + SeatsCount seatsCount = default; + var spaceBoundary = room.Boundary; + var wallCandidateOptions = WallGeneration.FindWallCandidateOptions(room, levelVolume?.Profile, corridorSegments); + var boundaryCurves = new List + { + spaceBoundary.Perimeter + }; + boundaryCurves.AddRange(spaceBoundary.Voids ?? new List()); + + var possibleConfigs = new List<(ConfigInfo configInfo, List<(Line Line, string Type)> wallCandidates)>(); + foreach (var (OrientationGuideEdge, WallCandidates) in wallCandidateOptions) + { + var orientationTransform = new Transform(Vector3.Origin, OrientationGuideEdge.Direction(), Vector3.ZAxis); + var grid = new Grid2d(boundaryCurves, orientationTransform); + foreach (var cell in grid.GetCells()) + { + var config = FindConfigByFit(configs, cell); + if (config != null) + { + possibleConfigs.Add((config.Value, WallCandidates)); + } + } + } + if (possibleConfigs.Any()) + { + var (configInfo, wallCandidates) = SelectTheBestOfPossibleConfigs(possibleConfigs); + + var layout = InstantiateLayoutByFit(configInfo, room.Transform); + SetLevelVolume(layout.Instance, levelVolume?.Id); + wallCandidateLines.AddRange(wallCandidates); + outputModel.AddElement(layout.Instance); + seatsCount = CountSeats(layout); + } + else if (configs.Count == 0) + { + wallCandidateLines.AddRange(wallCandidateOptions.First().WallCandidates); + } + + totalSeats += seatsCount.Seats; + outputModel.AddElement(new SpaceMetric(room.Id, seatsCount.Seats, seatsCount.Headcount, seatsCount.Desks, seatsCount.CollaborationSeats)); + } + + double height = levelVolume?.Height ?? 3; + Transform xform = levelVolume?.Transform ?? new Transform(); + + if (createWalls) + { + outputModel.AddElement(new InteriorPartitionCandidate(Guid.NewGuid()) + { + WallCandidateLines = wallCandidateLines, + Height = height, + LevelTransform = xform, + }); + } + } + OverrideUtilities.InstancePositionOverrides(overrides, outputModel); + + return new LayoutGenerationResult + { + SeatsCount = totalSeats, + OutputModel = outputModel + }; + } + + /// + /// Instantiate a space by finding the largest space that will fit from a SpaceConfiguration. + /// + /// The configuration containing all possible space arrangements + /// The width of the space to fill + /// The length of the space to fill + /// The more-or-less rectangular polygon to fill + /// A transform to apply to the rectangle. + /// + public ConfigInfo? FindConfigByFit(SpaceConfiguration configs, double width, double length, Polygon rectangle) + { + var selectedConfigPair = FindConfig(width, length, configs); + if (selectedConfigPair.HasValue) + { + return new ConfigInfo(selectedConfigPair?.Key, selectedConfigPair?.Value, rectangle); + } + return null; + } + + public LayoutInstantiated InstantiateLayoutByFit(ConfigInfo? selectedConfigInfo, Transform xform) + { + if (!selectedConfigInfo.HasValue) + { + return null; + } + var selectedConfig = selectedConfigInfo.Value.Config; + var selectedConfigName = selectedConfigInfo.Value.ConfigName; + var rules = selectedConfig.Rules(); + + var componentDefinition = new ComponentDefinition(rules, selectedConfig.Anchors()); + var instance = componentDefinition.Instantiate(ContentConfiguration.AnchorsFromRect(selectedConfigInfo.Value.Rectangle.TransformedPolygon(xform))); + return new LayoutInstantiated() { Instance = instance, Config = selectedConfig, ConfigName = selectedConfigName }; + } + + /// + /// Instantiate a space by finding the largest space that will fit a grid cell from a SpaceConfiguration. + /// + /// The configuration containing all possible space arrangements. + /// The 2d grid cell to fill. + /// A transform to apply to the rectangle. + /// + public ConfigInfo? FindConfigByFit(SpaceConfiguration configs, Grid2d cell) + { + var rect = cell.GetCellGeometry() as Polygon; + var segs = rect.Segments(); + var width = segs[0].Length(); + var depth = segs[1].Length(); + var trimmedGeo = cell.GetTrimmedCellGeometry(); + if (!cell.IsTrimmed() && trimmedGeo.Count() > 0) + { + return FindConfigByFit(configs, width, depth, rect); + } + else if (trimmedGeo.Count() > 0) + { + var largestTrimmedShape = trimmedGeo.OfType().OrderBy(s => s.Area()).Last(); + try + { + if (largestTrimmedShape.Vertices.Count < 8) + { + // LIR does a better job if there are more vertices to work with. + var vertices = new List(); + foreach (var segment in largestTrimmedShape.Segments()) + { + vertices.Add(segment.Start); + vertices.Add(segment.Mid()); + } + largestTrimmedShape = new Polygon(vertices); + } + // TODO: don't use XY — find two (or more) best guess axes + // from the convex hull or something. I get weird results + // from LIR for trianglish shapes that aren't XY aligned on + // any edge. + + // XY aligned + Elements.LIR.LargestInteriorRectangle.CalculateLargestInteriorRectangle(largestTrimmedShape, out var bstBounds1); + // Dominant-Axis aligned + var longestEdge = largestTrimmedShape.Segments().OrderByDescending(s => s.Length()).First(); + var transformToEdge = new Transform(longestEdge.Start, longestEdge.Direction(), Vector3.ZAxis); + var transformFromEdge = transformToEdge.Inverted(); + var largestTrimmedShapeAligned = largestTrimmedShape.TransformedPolygon(transformFromEdge); + Elements.LIR.LargestInteriorRectangle.CalculateLargestInteriorRectangle(largestTrimmedShapeAligned, out var bstBounds2); + var largestInteriorRect = bstBounds1.area > bstBounds2.area ? bstBounds1.Polygon : bstBounds2.Polygon.TransformedPolygon(transformToEdge); + var widthSeg = largestInteriorRect.Segments().OrderBy(s => s.Direction().Dot(segs[0].Direction())).Last(); + var depthSeg = largestInteriorRect.Segments().OrderBy(s => s.Direction().Dot(segs[1].Direction())).Last(); + width = widthSeg.Length(); + depth = depthSeg.Length(); + var reconstructedRect = new Polygon( + widthSeg.Start, + widthSeg.End, + widthSeg.End + depthSeg.Direction() * depth, + widthSeg.Start + depthSeg.Direction() * depth + ); + return FindConfigByFit(configs, width, depth, reconstructedRect); + } + catch + { + // largest interior rectangle failed. Just proceed. + } + var cinchedPoly = largestTrimmedShape; + if (largestTrimmedShape.Vertices.Count() > 4) + { + var cinchedVertices = rect.Vertices.Select(v => largestTrimmedShape.Vertices.OrderBy(v2 => v2.DistanceTo(v)).First()).ToList(); + cinchedPoly = new Polygon(cinchedVertices); + } + return FindConfigByFit(configs, width, depth, cinchedPoly); + } + return null; + } + + protected virtual SpaceConfiguration DeserializeConfigJson(string configJson) + { + return JsonConvert.DeserializeObject(configJson); + } + + protected virtual KeyValuePair? FindConfig(double width, double length, SpaceConfiguration configs) + { + var orderedConfigs = OrderConfigs(configs); + KeyValuePair? selectedConfigPair = null; + foreach (var configPair in orderedConfigs) + { + if (configPair.Value.CellBoundary.Width < width && configPair.Value.CellBoundary.Depth < length) + { + selectedConfigPair = configPair; + break; + } + } + + return selectedConfigPair; + } + + protected virtual IEnumerable> OrderConfigs(Dictionary configs) + { + return configs.OrderByDescending(kvp => kvp.Value.CellBoundary.Depth * kvp.Value.CellBoundary.Width); + } + + /// + /// Gets levels. It also assigns the relevant circulation segments to those levels. + /// + /// + /// + /// + protected virtual IEnumerable GetLevels(Dictionary inputModels, Model spacePlanningZones) + { + var levels = spacePlanningZones.AllElementsAssignableFromType(); + if (inputModels.TryGetValue("Circulation", out var circModel)) + { + var circSegments = circModel.AllElementsAssignableFromType(); + foreach (var cs in circSegments) + { + var matchingLevel = levels.FirstOrDefault(l => l.Level == cs.Level); + matchingLevel?.Elements.Add(cs); + } + } + + return levels; + } + + protected virtual (ConfigInfo? configInfo, List<(Line Line, string Type)> wallCandidates) SelectTheBestOfPossibleConfigs(List<(ConfigInfo configInfo, List<(Line Line, string Type)> wallCandidates)> possibleConfigs) + { + var distinctPossibleConfigs = possibleConfigs.DistinctBy(pc => pc.configInfo.ConfigName); + var orderedConfigs = OrderConfigs(distinctPossibleConfigs.Select(pc => pc.configInfo).ToDictionary(ci => ci.ConfigName, ci => ci.Config)); + var bestConfig = orderedConfigs.First(); + var bestConfiginfo = distinctPossibleConfigs.First(pc => pc.configInfo.ConfigName.Equals(bestConfig.Key)); + return bestConfiginfo; + } + + protected virtual SeatsCount CountSeats(LayoutInstantiated layoutInstantiated) + { + return new SeatsCount(0, 0, 0, 0); + } + + private static void SetLevelVolume(ComponentInstance componentInstance, Guid? levelVolumeId) + { + if (componentInstance != null) + { + foreach (var instance in componentInstance.Instances) + { + if (instance != null) + { + instance.AdditionalProperties["Level"] = levelVolumeId; + } + } + } + } + } +} \ No newline at end of file diff --git a/LayoutFunctions/LayoutFunctionCommon/LayoutInstantiated.cs b/LayoutFunctions/LayoutFunctionCommon/LayoutInstantiated.cs new file mode 100644 index 00000000..f3fba808 --- /dev/null +++ b/LayoutFunctions/LayoutFunctionCommon/LayoutInstantiated.cs @@ -0,0 +1,18 @@ +using Elements; +using Elements.Components; +using Elements.Geometry; +using Elements.Spatial; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +namespace LayoutFunctionCommon +{ + public class LayoutInstantiated + { + public ComponentInstance Instance { get; set; } + public ContentConfiguration Config { get; set; } + public string ConfigName { get; internal set; } + } +} \ No newline at end of file diff --git a/LayoutFunctions/LayoutFunctionCommon/LayoutStrategies.cs b/LayoutFunctions/LayoutFunctionCommon/LayoutStrategies.cs index cea77237..1de77f9a 100644 --- a/LayoutFunctions/LayoutFunctionCommon/LayoutStrategies.cs +++ b/LayoutFunctions/LayoutFunctionCommon/LayoutStrategies.cs @@ -9,13 +9,6 @@ using System.Linq; namespace LayoutFunctionCommon { - public class LayoutInstantiated - { - public ComponentInstance Instance { get; set; } - public ContentConfiguration Config { get; set; } - public string ConfigName { get; internal set; } - } - public static class LayoutStrategies { /// diff --git a/LayoutFunctions/MeetingRoomLayout/dependencies/CirculationSegment.cs b/LayoutFunctions/MeetingRoomLayout/dependencies/CirculationSegment.cs new file mode 100644 index 00000000..8fc4cbef --- /dev/null +++ b/LayoutFunctions/MeetingRoomLayout/dependencies/CirculationSegment.cs @@ -0,0 +1,12 @@ +using Elements; +using System; +using System.Linq; +using System.Collections.Generic; +using Elements.Geometry; +namespace Elements +{ + public partial class CirculationSegment : ICirculationSegment + { + + } +} \ No newline at end of file diff --git a/LayoutFunctions/MeetingRoomLayout/dependencies/LevelVolume.cs b/LayoutFunctions/MeetingRoomLayout/dependencies/LevelVolume.cs new file mode 100644 index 00000000..d5d3b095 --- /dev/null +++ b/LayoutFunctions/MeetingRoomLayout/dependencies/LevelVolume.cs @@ -0,0 +1,7 @@ +namespace Elements +{ + public partial class LevelVolume : ILevelVolume + { + + } +} \ No newline at end of file diff --git a/LayoutFunctions/MeetingRoomLayout/src/Function.g.cs b/LayoutFunctions/MeetingRoomLayout/src/Function.g.cs index 4822ae4f..b039e1f9 100644 --- a/LayoutFunctions/MeetingRoomLayout/src/Function.g.cs +++ b/LayoutFunctions/MeetingRoomLayout/src/Function.g.cs @@ -61,11 +61,11 @@ public async Task Handler(MeetingRoomLayoutInputs args Console.WriteLine($"Time to load assemblies: {sw.Elapsed.TotalSeconds})"); if(this.store == null) - { + { this.store = new S3ModelStore(RegionEndpoint.GetBySystemName("us-west-1")); } - var l = new InvocationWrapper (store, MeetingRoomLayout.Execute); + var l = new InvocationWrapper(store, MeetingRoomLayout.Execute); var output = await l.InvokeAsync(args); return output; } diff --git a/LayoutFunctions/MeetingRoomLayout/src/MeetingRoomLayout.cs b/LayoutFunctions/MeetingRoomLayout/src/MeetingRoomLayout.cs index 60249adf..f132a45b 100644 --- a/LayoutFunctions/MeetingRoomLayout/src/MeetingRoomLayout.cs +++ b/LayoutFunctions/MeetingRoomLayout/src/MeetingRoomLayout.cs @@ -14,176 +14,84 @@ namespace MeetingRoomLayout public static class MeetingRoomLayout { - private class LayoutInstantiated + private class MeetingLayoutGeneration : LayoutGeneration { - public ComponentInstance Instance { get; set; } - public RoomTally Tally { get; set; } - }; + private Dictionary seatsTable = new(); - /// - /// The MeetingRoomLayout function. - /// - /// The input model. - /// The arguments to the execution. - /// A MeetingRoomLayoutOutputs instance containing computed results and the model with any new elements. - public static MeetingRoomLayoutOutputs Execute(Dictionary inputModels, MeetingRoomLayoutInputs input) - { - Elements.Serialization.glTF.GltfExtensions.UseReferencedContentExtension = true; - var spacePlanningZones = inputModels["Space Planning Zones"]; - var levels = spacePlanningZones.AllElementsOfType(); - if (inputModels.TryGetValue("Circulation", out var circModel)) + protected override SeatsCount CountSeats(LayoutInstantiated layout) { - var circSegments = circModel.AllElementsOfType(); - foreach (var cs in circSegments) + int seatsCount = 0; + if (layout != null) { - var matchingLevel = levels.FirstOrDefault(l => l.Level == cs.Level); - if (matchingLevel != null) + if (configInfos.TryGetValue(layout.ConfigName, out var configInfo)) { - matchingLevel.Elements.Add(cs); + seatsCount = configInfo.capacity; } - } - } - var levelVolumes = LayoutStrategies.GetLevelVolumes(inputModels); - var configJson = File.ReadAllText("./ConferenceRoomConfigurations.json"); - var configs = JsonConvert.DeserializeObject(configJson); - - var outputModel = new Model(); - int totalSeats = 0; - var seatsTable = new Dictionary(); - foreach (var lvl in levels) - { - var corridors = lvl.Elements.OfType(); - var corridorSegments = corridors.SelectMany(p => p.Profile.Segments()); - var meetingRmBoundaries = lvl.Elements.OfType().Where(z => z.Name == "Meeting Room"); - var levelVolume = levelVolumes.FirstOrDefault(l => - (lvl.AdditionalProperties.TryGetValue("LevelVolumeId", out var levelVolumeId) && - levelVolumeId as string == l.Id.ToString())) ?? - levelVolumes.FirstOrDefault(l => l.Name == lvl.Name); - - var wallCandidateLines = new List<(Line line, string type)>(); - foreach (var room in meetingRmBoundaries) - { - var seatsCount = 0; - var spaceBoundary = room.Boundary; - var levelInvertedTransform = levelVolume?.Transform.Inverted() ?? new Transform(); - var roomWallCandidatesLines = WallGeneration.FindWallCandidates(room, levelVolume?.Profile, corridorSegments, out Line orientationGuideEdge) - .Select(c => (c.line.TransformedLine(levelInvertedTransform), c.type)); - wallCandidateLines.AddRange(roomWallCandidatesLines); - - var orientationTransform = new Transform(Vector3.Origin, orientationGuideEdge.Direction(), Vector3.ZAxis); - var boundaryCurves = new List(); - boundaryCurves.Add(spaceBoundary.Perimeter); - boundaryCurves.AddRange(spaceBoundary.Voids ?? new List()); - try + if (seatsTable.ContainsKey(layout.ConfigName)) { - var grid = new Grid2d(boundaryCurves, orientationTransform); - foreach (var cell in grid.GetCells()) - { - var rect = cell.GetCellGeometry() as Polygon; - var segs = rect.Segments(); - var width = segs[0].Length(); - var depth = segs[1].Length(); - var trimmedGeo = cell.GetTrimmedCellGeometry(); - if (!cell.IsTrimmed() && trimmedGeo.Count() > 0) - { - var layout = InstantiateLayout(configs, width, depth, rect, room.Transform); - seatsCount += AddInstantiatedLayout(layout, outputModel, seatsTable, levelVolume); - } - else if (trimmedGeo.Count() > 0) - { - var largestTrimmedShape = trimmedGeo.OfType().OrderBy(s => s.Area()).Last(); - var cinchedVertices = rect.Vertices.Select(v => largestTrimmedShape.Vertices.OrderBy(v2 => v2.DistanceTo(v)).First()).ToList(); - var cinchedPoly = new Polygon(cinchedVertices); - var layout = InstantiateLayout(configs, width, depth, cinchedPoly, room.Transform); - seatsCount += AddInstantiatedLayout(layout, outputModel, seatsTable, levelVolume); - } - } + seatsTable[layout.ConfigName].SeatsCount += seatsCount; } - catch + else { - Console.WriteLine("Error generating layout for room " + room.Id); + seatsTable[layout.ConfigName] = new RoomTally(layout.ConfigName, seatsCount); } - - totalSeats += seatsCount; - outputModel.AddElement(new SpaceMetric(room.Id, seatsCount, 0, 0, 0)); - } - - var height = meetingRmBoundaries.FirstOrDefault()?.Height ?? 3; - if (input.CreateWalls) - { - outputModel.AddElement(new InteriorPartitionCandidate(Guid.NewGuid()) - { - WallCandidateLines = wallCandidateLines, - Height = height, - LevelTransform = levelVolume?.Transform ?? new Transform() - }); } + return new SeatsCount(seatsCount, 0, 0, 0); } - OverrideUtilities.InstancePositionOverrides(input.Overrides, outputModel); - - outputModel.AddElements(seatsTable.Select(kvp => kvp.Value).OrderByDescending(a => a.SeatsCount)); - - var output = new MeetingRoomLayoutOutputs(totalSeats); - output.Model = outputModel; - return output; - } - private static int AddInstantiatedLayout(LayoutInstantiated layout, Model model, Dictionary seatsTable, LevelVolume levelVolume) - { - if (layout == null) + public override LayoutGenerationResult StandardLayoutOnAllLevels(string programTypeName, Dictionary inputModels, dynamic overrides, bool createWalls, string configurationsPath, string catalogPath = "catalog.json") { - return 0; + seatsTable = new Dictionary(); + var result = base.StandardLayoutOnAllLevels(programTypeName, inputModels, (object)overrides, createWalls, configurationsPath, catalogPath); + result.OutputModel.AddElements(seatsTable.Select(kvp => kvp.Value).OrderByDescending(a => a.SeatsCount)); + return result; } - LayoutStrategies.SetLevelVolume(layout.Instance, levelVolume?.Id); - model.AddElement(layout.Instance); - - int seatsCount = 0; - if (layout.Tally != null) + protected override IEnumerable> OrderConfigs(Dictionary configs) { - if (seatsTable.ContainsKey(layout.Tally.RoomType)) - { - seatsTable[layout.Tally.RoomType].SeatsCount += layout.Tally.SeatsCount; - } - else + return configs.OrderBy(i => { - seatsTable[layout.Tally.RoomType] = new RoomTally(layout.Tally.RoomType, layout.Tally.SeatsCount); - } - seatsCount = layout.Tally.SeatsCount; + if (!configInfos.ContainsKey(i.Key)) + { + return int.MaxValue; + } + return configInfos[i.Key].orderIndex; + }); } - - return seatsCount; } - private static LayoutInstantiated InstantiateLayout(SpaceConfiguration configs, double width, double length, Polygon rectangle, Transform xform) + /// + /// The MeetingRoomLayout function. + /// + /// The input model. + /// The arguments to the execution. + /// A MeetingRoomLayoutOutputs instance containing computed results and the model with any new elements. + public static MeetingRoomLayoutOutputs Execute(Dictionary inputModels, MeetingRoomLayoutInputs input) { - ContentConfiguration selectedConfig = null; - var result = new LayoutInstantiated(); - for (int i = 0; i < orderedKeys.Length; ++i) - { - var config = configs[orderedKeys[i]]; - if (config.CellBoundary.Width < width && config.CellBoundary.Depth < length) - { - selectedConfig = config; - result.Tally = new RoomTally(orderedKeys[i], orderedKeysCapacity[i]); - break; - } - } - if (selectedConfig == null) + Elements.Serialization.glTF.GltfExtensions.UseReferencedContentExtension = true; + var layoutGeneration = new MeetingLayoutGeneration(); + var result = layoutGeneration.StandardLayoutOnAllLevels("Meeting Room", inputModels, input.Overrides, false, "./ConferenceRoomConfigurations.json"); + var output = new MeetingRoomLayoutOutputs { - return null; - } - var baseRectangle = Polygon.Rectangle(selectedConfig.CellBoundary.Min, selectedConfig.CellBoundary.Max); - var rules = selectedConfig.Rules(); - - var componentDefinition = new ComponentDefinition(rules, selectedConfig.Anchors()); - result.Instance = componentDefinition.Instantiate(ContentConfiguration.AnchorsFromRect(rectangle.TransformedPolygon(xform))); - return result; + Model = result.OutputModel, + TotalSeatCount = result.SeatsCount + }; + return output; } - private static string[] orderedKeys = new[] { "22P", "20P", "14P", "13P", "8P", "6P-A", "6P-B", "4P-A", "4P-B" }; - private static int[] orderedKeysCapacity = new[] { 22, 20, 14, 13, 8, 6, 6, 4, 4 }; + private static readonly Dictionary configInfos = new() + { + {"22P", (22, 1)}, + {"20P", (20, 2)}, + {"14P", (14, 3)}, + {"13P", (13, 4)}, + {"8P", (8, 5)}, + {"6P-A", (6, 6)}, + {"6P-B", (6, 7)}, + {"4P-A", (4, 8)}, + {"4P-B", (4, 9)} + }; } } \ No newline at end of file diff --git a/LayoutFunctions/OpenCollabLayout/dependencies/CirculationSegment.cs b/LayoutFunctions/OpenCollabLayout/dependencies/CirculationSegment.cs new file mode 100644 index 00000000..8fc4cbef --- /dev/null +++ b/LayoutFunctions/OpenCollabLayout/dependencies/CirculationSegment.cs @@ -0,0 +1,12 @@ +using Elements; +using System; +using System.Linq; +using System.Collections.Generic; +using Elements.Geometry; +namespace Elements +{ + public partial class CirculationSegment : ICirculationSegment + { + + } +} \ No newline at end of file diff --git a/LayoutFunctions/OpenCollabLayout/dependencies/LevelElements.cs b/LayoutFunctions/OpenCollabLayout/dependencies/LevelElements.cs new file mode 100644 index 00000000..954ca012 --- /dev/null +++ b/LayoutFunctions/OpenCollabLayout/dependencies/LevelElements.cs @@ -0,0 +1,6 @@ +namespace Elements +{ + public partial class LevelElements : Element, ILevelElements + { + } +} \ No newline at end of file diff --git a/LayoutFunctions/OpenCollabLayout/dependencies/SpaceBoundary.cs b/LayoutFunctions/OpenCollabLayout/dependencies/SpaceBoundary.cs new file mode 100644 index 00000000..ed244d1f --- /dev/null +++ b/LayoutFunctions/OpenCollabLayout/dependencies/SpaceBoundary.cs @@ -0,0 +1,9 @@ +using Elements.Geometry; +namespace Elements +{ + public partial class SpaceBoundary : GeometricElement, ISpaceBoundary + { + public Vector3? ParentCentroid { get; set; } + + } +} \ No newline at end of file diff --git a/LayoutFunctions/OpenCollabLayout/src/Function.g.cs b/LayoutFunctions/OpenCollabLayout/src/Function.g.cs index ace802d9..226c7394 100644 --- a/LayoutFunctions/OpenCollabLayout/src/Function.g.cs +++ b/LayoutFunctions/OpenCollabLayout/src/Function.g.cs @@ -61,11 +61,11 @@ public async Task Handler(OpenCollaborationLayou Console.WriteLine($"Time to load assemblies: {sw.Elapsed.TotalSeconds})"); if(this.store == null) - { + { this.store = new S3ModelStore(RegionEndpoint.GetBySystemName("us-west-1")); } - var l = new InvocationWrapper (store, OpenCollaborationLayout.Execute); + var l = new InvocationWrapper(store, OpenCollaborationLayout.Execute); var output = await l.InvokeAsync(args); return output; } diff --git a/LayoutFunctions/OpenCollabLayout/src/OpenCollaborationLayout.cs b/LayoutFunctions/OpenCollabLayout/src/OpenCollaborationLayout.cs index cb9645b2..ea8e6994 100644 --- a/LayoutFunctions/OpenCollabLayout/src/OpenCollaborationLayout.cs +++ b/LayoutFunctions/OpenCollabLayout/src/OpenCollaborationLayout.cs @@ -13,228 +13,121 @@ namespace OpenCollaborationLayout { public static class OpenCollaborationLayout { - /// - /// The OpenCollaborationLayout function. - /// - /// The input model. - /// The arguments to the execution. - /// A OpenCollaborationLayoutOutputs instance containing computed results and the model with any new elements. - public static OpenCollaborationLayoutOutputs Execute(Dictionary inputModels, OpenCollaborationLayoutInputs input) + private class OpenCollaborationLayoutGeneration : LayoutGeneration { - Elements.Serialization.glTF.GltfExtensions.UseReferencedContentExtension = true; - varietyCounter = 0; - int totalCountableSeats = 0; - var spacePlanningZones = inputModels["Space Planning Zones"]; - - var hasOpenOffice = inputModels.TryGetValue("Open Office Layout", out var openOfficeModel); + private Dictionary configsWithUsedCount = new(); - var levels = spacePlanningZones.AllElementsOfType(); - if (inputModels.TryGetValue("Circulation", out var circModel)) + protected override SeatsCount CountSeats(LayoutInstantiated layout) { - var circSegments = circModel.AllElementsOfType(); - foreach (var cs in circSegments) + if (layout.Config is ConfigurationWithCounts configWithCounts) { - var matchingLevel = levels.FirstOrDefault(l => l.Level == cs.Level); - if (matchingLevel != null) - { - matchingLevel.Elements.Add(cs); - } + return new SeatsCount(configWithCounts.SeatCount, 0, 0, configWithCounts.SeatCount); } - } - - var levelVolumes = LayoutStrategies.GetLevelVolumes(inputModels); - var output = new OpenCollaborationLayoutOutputs(); - var configJson = File.ReadAllText("./OpenCollaborationConfigurations.json"); - var configs = JsonConvert.DeserializeObject>(configJson); - if (hasOpenOffice) - { - foreach (var sb in openOfficeModel.AllElementsOfType()) - { - if (sb.AdditionalProperties.TryGetValue("Parent Level Id", out var lvlId)) - { - var matchingLevel = levels.FirstOrDefault(l => l.Id.ToString() == lvlId as string); - matchingLevel?.Elements.Add(sb); - } - } + return default; } - foreach (var lvl in levels) + protected override (ConfigInfo? configInfo, List<(Line Line, string Type)> wallCandidates) SelectTheBestOfPossibleConfigs(List<(ConfigInfo configInfo, List<(Line Line, string Type)> wallCandidates)> possibleConfigs) { - var corridors = lvl.Elements.OfType(); - var corridorSegments = corridors.SelectMany(p => p.Profile.Segments()); - var meetingRmBoundaries = lvl.Elements.OfType().Where(z => z.Name == "Open Collaboration"); - var levelVolume = levelVolumes.FirstOrDefault(l => - (lvl.AdditionalProperties.TryGetValue("LevelVolumeId", out var levelVolumeId) && - levelVolumeId as string == l.Id.ToString())) ?? - levelVolumes.FirstOrDefault(l => l.Name == lvl.Name); - - foreach (var room in meetingRmBoundaries) + var configsThatFitWell = new List<(ConfigInfo configInfo, int usedCount, List<(Line Line, string Type)> wallCandidates)>(); + var orderedConfigPairs = configsWithUsedCount.OrderByDescending(kvp => kvp.Value.config.CellBoundary.Depth * kvp.Value.config.CellBoundary.Width); + possibleConfigs = possibleConfigs.DistinctBy(pc => pc.configInfo.ConfigName).ToList(); + foreach (var (configInfo, wallCandidates) in possibleConfigs) { - var seatsCount = 0; - var spaceBoundary = room.Boundary; - Line orientationGuideEdge = FindEdgeAdjacentToSegments(spaceBoundary.Perimeter.Segments(), corridorSegments, out var wallCandidates); - var orientationTransform = new Transform(Vector3.Origin, orientationGuideEdge.Direction(), Vector3.ZAxis); - var boundaryCurves = new List(); - boundaryCurves.Add(spaceBoundary.Perimeter); - boundaryCurves.AddRange(spaceBoundary.Voids ?? new List()); - - var grid = new Grid2d(boundaryCurves, orientationTransform); - foreach (var cell in grid.GetCells()) + var width = configInfo.Config.Width; + var length = configInfo.Config.Depth; + foreach (var configPair in orderedConfigPairs) { - var rect = cell.GetCellGeometry() as Polygon; - var segs = rect.Segments(); - var width = segs[0].Length(); - var depth = segs[1].Length(); - var trimmedGeo = cell.GetTrimmedCellGeometry(); - if (!cell.IsTrimmed() && trimmedGeo.Length > 0) - { - var (instance, count) = InstantiateLayout(configs, width, depth, rect, room.Transform); - LayoutStrategies.SetLevelVolume(instance, levelVolume?.Id); - output.Model.AddElement(instance); - seatsCount += count; - } - else if (trimmedGeo.Length > 0) + var configName = configPair.Key; + var config = configPair.Value.config; + // if it fits + if (config.CellBoundary.Width <= width && config.CellBoundary.Depth <= length) { - var largestTrimmedShape = trimmedGeo.OfType().OrderBy(s => s.Area()).Last(); - var cinchedVertices = rect.Vertices.Select(v => largestTrimmedShape.Vertices.OrderBy(v2 => v2.DistanceTo(v)).First()).ToList(); - var cinchedPoly = new Polygon(cinchedVertices); - // output.Model.AddElement(new ModelCurve(cinchedPoly, BuiltInMaterials.ZAxis, levelVolume.Transform)); - try + if (configsThatFitWell.Count == 0) { - var (instance, count) = InstantiateLayout(configs, width, depth, cinchedPoly, room.Transform); - LayoutStrategies.SetLevelVolume(instance, levelVolume?.Id); - output.Model.AddElement(instance); - seatsCount += count; + configsThatFitWell.Add((new ConfigInfo(configName, config, configInfo.Rectangle), configPair.Value.usedCount, wallCandidates)); } - catch (Exception e) + else { - Console.WriteLine("Failed to instantiate config\n" + e.ToString()); + // check if there's another config that's roughly the same size + if (config.CellBoundary.Width.ApproximatelyEquals(configInfo.Config.CellBoundary.Width, 1.0) + && config.CellBoundary.Depth.ApproximatelyEquals(configInfo.Config.CellBoundary.Depth, 1.0)) + { + configsThatFitWell.Add((new ConfigInfo(configName, config, configInfo.Rectangle), configPair.Value.usedCount, wallCandidates)); + } + } - Console.WriteLine("🤷‍♂️ funny shape!!!"); } } - - totalCountableSeats += seatsCount; - output.Model.AddElement(new SpaceMetric(room.Id, seatsCount, 0, 0, seatsCount)); } + // shouldn't happen + if (configsThatFitWell.Count == 0) + { + return possibleConfigs.First(); + } + + var selectedConfig = configsThatFitWell + .OrderBy(c => c.usedCount) + .ThenByDescending(kvp => kvp.configInfo.Config.CellBoundary.Depth * kvp.configInfo.Config.CellBoundary.Width) + .First(); + configsWithUsedCount[selectedConfig.configInfo.ConfigName] = (selectedConfig.configInfo.Config, selectedConfig.usedCount + 1); + return (selectedConfig.configInfo, selectedConfig.wallCandidates); } - output.CollaborationSeats = totalCountableSeats; - OverrideUtilities.InstancePositionOverrides(input.Overrides, output.Model); - return output; - } - private static Line FindEdgeAdjacentToSegments(IEnumerable edgesToClassify, IEnumerable corridorSegments, out IEnumerable otherSegments, double maxDist = 0) - { - var minDist = double.MaxValue; - var minSeg = edgesToClassify.First(); - var allEdges = edgesToClassify.ToList(); - var selectedIndex = 0; - for (int i = 0; i < allEdges.Count; i++) + protected override SpaceConfiguration DeserializeConfigJson(string configJson) { - var edge = allEdges[i]; - var midpt = edge.Mid(); - foreach (var seg in corridorSegments) + var spaceConfiguration = new SpaceConfiguration(); + var dictWithCounts = JsonConvert.DeserializeObject>(configJson); + foreach (var pair in dictWithCounts) { - var dist = midpt.DistanceTo(seg); - // if two segments are basically the same distance to the corridor segment, - // prefer the longer one. - if (Math.Abs(dist - minDist) < 0.1) - { - minDist = dist; - if (minSeg.Length() < edge.Length()) - { - minSeg = edge; - selectedIndex = i; - } - } - else if (dist < minDist) - { - minDist = dist; - minSeg = edge; - selectedIndex = i; - } + spaceConfiguration.Add(pair.Key, pair.Value); } + + configsWithUsedCount = spaceConfiguration.ToDictionary(kvp => kvp.Key, kvp => (kvp.Value, 0)); + return spaceConfiguration; } - if (maxDist != 0) - { - if (minDist < maxDist) - { - otherSegments = Enumerable.Range(0, allEdges.Count).Except(new[] { selectedIndex }).Select(i => allEdges[i]); - return minSeg; - } - else - { - Console.WriteLine($"no matches: {minDist}"); - otherSegments = allEdges; - return null; - } + public override LayoutGenerationResult StandardLayoutOnAllLevels(string programTypeName, Dictionary inputModels, dynamic overrides, bool createWalls, string configurationsPath, string catalogPath = "catalog.json") + { + var result = base.StandardLayoutOnAllLevels(programTypeName, inputModels, (object)overrides, createWalls, configurationsPath, catalogPath); + return result; } - otherSegments = Enumerable.Range(0, allEdges.Count).Except(new[] { selectedIndex }).Select(i => allEdges[i]); - return minSeg; - } - private static int varietyCounter = 0; - private static (ComponentInstance instance, int count) InstantiateLayout(Dictionary configs, double width, double length, Polygon rectangle, Transform xform) - { - var orderedKeys = configs.OrderByDescending(kvp => kvp.Value.CellBoundary.Depth * kvp.Value.CellBoundary.Width).Select(kvp => kvp.Key); - var configsThatFitWell = new List(); - int countableSeatCount = 0; - foreach (var key in orderedKeys) + protected override IEnumerable GetLevels(Dictionary inputModels, Model spacePlanningZones) { - var config = configs[key]; - // if it fits - if (config.CellBoundary.Width < width && config.CellBoundary.Depth < length) + var levels = base.GetLevels(inputModels, spacePlanningZones); + if (inputModels.TryGetValue("Open Office Layout", out var openOfficeModel)) { - if (configsThatFitWell.Count == 0) - { - configsThatFitWell.Add(config); - } - else + foreach (var sb in openOfficeModel.AllElementsOfType()) { - var firstFittingConfig = configsThatFitWell.First(); - // check if there's another config that's roughly the same size - if (config.CellBoundary.Width.ApproximatelyEquals(firstFittingConfig.CellBoundary.Width, 1.0) && config.CellBoundary.Depth.ApproximatelyEquals(firstFittingConfig.CellBoundary.Depth, 1.0)) + if (sb.AdditionalProperties.TryGetValue("Parent Level Id", out var lvlId)) { - configsThatFitWell.Add(config); + var matchingLevel = levels.FirstOrDefault(l => l.Id.ToString() == lvlId as string); + matchingLevel?.Elements.Add(sb); } } } + return levels; } - if (configsThatFitWell.Count == 0) - { - return (null, 0); - } - var selectedConfig = configsThatFitWell[varietyCounter % configsThatFitWell.Count]; - - countableSeatCount = selectedConfig.SeatCount; - - var baseRectangle = Polygon.Rectangle(selectedConfig.CellBoundary.Min, selectedConfig.CellBoundary.Max); - - var rules = selectedConfig.Rules(); - varietyCounter++; - var componentDefinition = new ComponentDefinition(rules, selectedConfig.Anchors()); - var instance = componentDefinition.Instantiate(ContentConfiguration.AnchorsFromRect(rectangle.TransformedPolygon(xform))); - var allPlacedInstances = instance.Instances; - return (instance, countableSeatCount); } - private static int CountConfigSeats(ContentConfiguration config, string[] countableSeaters, int seatsPerSeater) + /// + /// The OpenCollaborationLayout function. + /// + /// The input model. + /// The arguments to the execution. + /// A OpenCollaborationLayoutOutputs instance containing computed results and the model with any new elements. + public static OpenCollaborationLayoutOutputs Execute(Dictionary inputModels, OpenCollaborationLayoutInputs input) { - int countableSeatCount = 0; - foreach (var item in config.ContentItems) + Elements.Serialization.glTF.GltfExtensions.UseReferencedContentExtension = true; + var layoutGeneration = new OpenCollaborationLayoutGeneration(); + var result = layoutGeneration.StandardLayoutOnAllLevels("Open Collaboration", inputModels, input.Overrides, false, "./OpenCollaborationConfigurations.json"); + var output = new OpenCollaborationLayoutOutputs { - foreach (var countableSeat in countableSeaters) - { - if (item.ContentElement.GltfLocation.Contains(countableSeat)) - { - countableSeatCount += seatsPerSeater; - break; - } - } - } - return countableSeatCount; + Model = result.OutputModel, + CollaborationSeats = result.SeatsCount + }; + return output; } } } \ No newline at end of file diff --git a/LayoutFunctions/PantryLayout/dependencies/CirculationSegment.cs b/LayoutFunctions/PantryLayout/dependencies/CirculationSegment.cs new file mode 100644 index 00000000..8fc4cbef --- /dev/null +++ b/LayoutFunctions/PantryLayout/dependencies/CirculationSegment.cs @@ -0,0 +1,12 @@ +using Elements; +using System; +using System.Linq; +using System.Collections.Generic; +using Elements.Geometry; +namespace Elements +{ + public partial class CirculationSegment : ICirculationSegment + { + + } +} \ No newline at end of file diff --git a/LayoutFunctions/PantryLayout/dependencies/LevelElements.cs b/LayoutFunctions/PantryLayout/dependencies/LevelElements.cs new file mode 100644 index 00000000..954ca012 --- /dev/null +++ b/LayoutFunctions/PantryLayout/dependencies/LevelElements.cs @@ -0,0 +1,6 @@ +namespace Elements +{ + public partial class LevelElements : Element, ILevelElements + { + } +} \ No newline at end of file diff --git a/LayoutFunctions/PantryLayout/dependencies/LevelVolume.cs b/LayoutFunctions/PantryLayout/dependencies/LevelVolume.cs new file mode 100644 index 00000000..d5d3b095 --- /dev/null +++ b/LayoutFunctions/PantryLayout/dependencies/LevelVolume.cs @@ -0,0 +1,7 @@ +namespace Elements +{ + public partial class LevelVolume : ILevelVolume + { + + } +} \ No newline at end of file diff --git a/LayoutFunctions/PantryLayout/dependencies/SpaceBoundary.cs b/LayoutFunctions/PantryLayout/dependencies/SpaceBoundary.cs new file mode 100644 index 00000000..ed244d1f --- /dev/null +++ b/LayoutFunctions/PantryLayout/dependencies/SpaceBoundary.cs @@ -0,0 +1,9 @@ +using Elements.Geometry; +namespace Elements +{ + public partial class SpaceBoundary : GeometricElement, ISpaceBoundary + { + public Vector3? ParentCentroid { get; set; } + + } +} \ No newline at end of file diff --git a/LayoutFunctions/PantryLayout/src/Function.g.cs b/LayoutFunctions/PantryLayout/src/Function.g.cs index 8884df35..d4d40e7a 100644 --- a/LayoutFunctions/PantryLayout/src/Function.g.cs +++ b/LayoutFunctions/PantryLayout/src/Function.g.cs @@ -61,11 +61,11 @@ public async Task Handler(PantryLayoutInputs args, ILambdaC Console.WriteLine($"Time to load assemblies: {sw.Elapsed.TotalSeconds})"); if(this.store == null) - { + { this.store = new S3ModelStore(RegionEndpoint.GetBySystemName("us-west-1")); } - var l = new InvocationWrapper (store, PantryLayout.Execute); + var l = new InvocationWrapper(store, PantryLayout.Execute); var output = await l.InvokeAsync(args); return output; } diff --git a/LayoutFunctions/PantryLayout/src/PantryLayout.cs b/LayoutFunctions/PantryLayout/src/PantryLayout.cs index 75b09c2e..90bff35a 100644 --- a/LayoutFunctions/PantryLayout/src/PantryLayout.cs +++ b/LayoutFunctions/PantryLayout/src/PantryLayout.cs @@ -14,192 +14,52 @@ namespace PantryLayout { public static class PantryLayout { - /// - /// The PantryLayout function. - /// - /// The input model. - /// The arguments to the execution. - /// A PantryLayoutOutputs instance containing computed results and the model with any new elements. - public static PantryLayoutOutputs Execute(Dictionary inputModels, PantryLayoutInputs input) + private class PantryLayoutGeneration : LayoutGeneration { - Elements.Serialization.glTF.GltfExtensions.UseReferencedContentExtension = true; - - var spacePlanningZones = inputModels["Space Planning Zones"]; - var levels = spacePlanningZones.AllElementsOfType(); - if (inputModels.TryGetValue("Circulation", out var circModel)) + private static readonly string[] countableSeats = new[] { - var circSegments = circModel.AllElementsOfType(); - foreach (var cs in circSegments) - { - var matchingLevel = levels.FirstOrDefault(l => l.Level == cs.Level); - if (matchingLevel != null) - { - matchingLevel.Elements.Add(cs); - } - } - } - var levelVolumes = LayoutStrategies.GetLevelVolumes(inputModels); - var outputModel = new Model(); - var configJson = File.ReadAllText("./PantryConfigurations.json"); - var configs = JsonConvert.DeserializeObject(configJson); - int totalCountableSeats = 0; - - foreach (var lvl in levels) - { - var corridors = lvl.Elements.OfType(); - var corridorSegments = corridors.SelectMany(p => p.Profile.Segments()); - var meetingRmBoundaries = lvl.Elements.OfType().Where(z => z.Name == "Pantry"); - var levelVolume = levelVolumes.FirstOrDefault(l => - (lvl.AdditionalProperties.TryGetValue("LevelVolumeId", out var levelVolumeId) && - levelVolumeId as string == l.Id.ToString())) ?? - levelVolumes.FirstOrDefault(l => l.Name == lvl.Name); - - foreach (var room in meetingRmBoundaries) - { - var seatsCount = 0; - var spaceBoundary = room.Boundary; - Line orientationGuideEdge = FindEdgeAdjacentToSegments(spaceBoundary.Perimeter.Segments(), corridorSegments, out var wallCandidates); - - var orientationTransform = new Transform(Vector3.Origin, orientationGuideEdge.Direction(), Vector3.ZAxis); - var boundaryCurves = new List(); - boundaryCurves.Add(spaceBoundary.Perimeter); - boundaryCurves.AddRange(spaceBoundary.Voids ?? new List()); - - var grid = new Grid2d(boundaryCurves, orientationTransform); - foreach (var cell in grid.GetCells()) - { - var rect = cell.GetCellGeometry() as Polygon; - var segs = rect.Segments(); - var width = segs[0].Length(); - var depth = segs[1].Length(); - var trimmedGeo = cell.GetTrimmedCellGeometry(); - if (!cell.IsTrimmed() && trimmedGeo.Count() > 0) - { - var layout = InstantiateLayout(configs, width, depth, rect, room.Transform); - LayoutStrategies.SetLevelVolume(layout.instance, levelVolume?.Id); - outputModel.AddElement(layout.instance); - seatsCount += layout.count; - } - else if (trimmedGeo.Count() > 0) - { - var largestTrimmedShape = trimmedGeo.OfType().OrderBy(s => s.Area()).Last(); - var cinchedVertices = rect.Vertices.Select(v => largestTrimmedShape.Vertices.OrderBy(v2 => v2.DistanceTo(v)).First()).ToList(); - var cinchedPoly = new Polygon(cinchedVertices); - // output.Model.AddElement(new ModelCurve(cinchedPoly, BuiltInMaterials.ZAxis, levelVolume.Transform)); - - var layout = InstantiateLayout(configs, width, depth, cinchedPoly, room.Transform); - LayoutStrategies.SetLevelVolume(layout.instance, levelVolume?.Id); - outputModel.AddElement(layout.instance); - - seatsCount += layout.count; - Console.WriteLine("🤷‍♂️ funny shape!!!"); - } - } - - totalCountableSeats += seatsCount; - outputModel.AddElement(new SpaceMetric(room.Id, seatsCount, 0, 0, 0)); - } - } - OverrideUtilities.InstancePositionOverrides(input.Overrides, outputModel); - - - var output = new PantryLayoutOutputs(totalCountableSeats); - output.Model = outputModel; - return output; - } + "Steelcase - Seating - Nooi - Cafeteria Chair - Chair", + "Steelcase - Seating - Nooi - Stool - Bar Height", + "Steelcase Turnstone - Shortcut X Base - Chair - Chair", + "Steelcase Turnstone - Shortcut X Base - Stool - Chair" + }; - private static Line FindEdgeAdjacentToSegments(IEnumerable edgesToClassify, IEnumerable corridorSegments, out IEnumerable otherSegments, double maxDist = 0) - { - var minDist = double.MaxValue; - var minSeg = edgesToClassify.First(); - var allEdges = edgesToClassify.ToList(); - var selectedIndex = 0; - for (int i = 0; i < allEdges.Count; i++) + protected override SeatsCount CountSeats(LayoutInstantiated layout) { - var edge = allEdges[i]; - var midpt = edge.PointAt(0.5); - foreach (var seg in corridorSegments) + int countableSeatCount = 0; + foreach (var item in layout.Config.ContentItems) { - var dist = midpt.DistanceTo(seg); - // if two segments are basically the same distance to the corridor segment, - // prefer the longer one. - if (Math.Abs(dist - minDist) < 0.1) + foreach (var countableSeat in countableSeats) { - minDist = dist; - if (minSeg.Length() < edge.Length()) + if (item.ContentElement.Name.Contains(countableSeat)) { - minSeg = edge; - selectedIndex = i; + countableSeatCount++; } } - else if (dist < minDist) - { - minDist = dist; - minSeg = edge; - selectedIndex = i; - } } - } - if (maxDist != 0) - { - if (minDist < maxDist) - { - otherSegments = Enumerable.Range(0, allEdges.Count).Except(new[] { selectedIndex }).Select(i => allEdges[i]); - return minSeg; - } - else - { - Console.WriteLine($"no matches: {minDist}"); - otherSegments = allEdges; - return null; - } + return new SeatsCount(countableSeatCount, 0, 0, 0); } - otherSegments = Enumerable.Range(0, allEdges.Count).Except(new[] { selectedIndex }).Select(i => allEdges[i]); - return minSeg; } - private static (ComponentInstance instance, int count) InstantiateLayout(SpaceConfiguration configs, double width, double length, Polygon rectangle, Transform xform) - { - string[] countableSeats = new[] { "Steelcase - Seating - Nooi - Cafeteria Chair - Chair", - "Steelcase - Seating - Nooi - Stool - Bar Height", - "Steelcase Turnstone - Shortcut X Base - Chair - Chair", - "Steelcase Turnstone - Shortcut X Base - Stool - Chair" }; - int countableSeatCount = 0; - ContentConfiguration selectedConfig = null; - var orderedKeys = configs.OrderByDescending(kvp => kvp.Value.CellBoundary.Depth * kvp.Value.CellBoundary.Width).Select(kvp => kvp.Key); - foreach (var key in orderedKeys) - { - var config = configs[key]; - if (config.CellBoundary.Width < width && config.CellBoundary.Depth < length) - { - foreach (var item in config.ContentItems) - { - foreach (var countableSeat in countableSeats) - { - if (item.ContentElement.Name.Contains(countableSeat)) - { - countableSeatCount++; - } - } - } + /// + /// The PantryLayout function. + /// + /// The input model. + /// The arguments to the execution. + /// A PantryLayoutOutputs instance containing computed results and the model with any new elements. + public static PantryLayoutOutputs Execute(Dictionary inputModels, PantryLayoutInputs input) + { + Elements.Serialization.glTF.GltfExtensions.UseReferencedContentExtension = true; - selectedConfig = config; - break; - } - } - if (selectedConfig == null) + var layoutGeneration = new PantryLayoutGeneration(); + var result = layoutGeneration.StandardLayoutOnAllLevels("Pantry", inputModels, input.Overrides, false, "./PantryConfigurations.json"); + var output = new PantryLayoutOutputs { - return (null, 0); - } - var baseRectangle = Polygon.Rectangle(selectedConfig.CellBoundary.Min, selectedConfig.CellBoundary.Max); - var rules = selectedConfig.Rules(); - - var componentDefinition = new ComponentDefinition(rules, selectedConfig.Anchors()); - var instance = componentDefinition.Instantiate(ContentConfiguration.AnchorsFromRect(rectangle.TransformedPolygon(xform))); - var allPlacedInstances = instance.Instances; - return (instance, countableSeatCount); + Model = result.OutputModel, + TotalCafeChairsCount = result.SeatsCount + }; + return output; } } - } \ No newline at end of file diff --git a/ZonePlanningFunctions/Function-SpacePlanning b/ZonePlanningFunctions/Function-SpacePlanning index 525e3129..bef1f941 160000 --- a/ZonePlanningFunctions/Function-SpacePlanning +++ b/ZonePlanningFunctions/Function-SpacePlanning @@ -1 +1 @@ -Subproject commit 525e31291c4907a794e13365f94c15c3a4b442f8 +Subproject commit bef1f941b93e692e7d968412f7d04b0b279bd5c9