/// <summary> /// Apply Vantage Streets edit overrides to a collection of existing elements /// </summary> /// <param name="overrideData">The Vantage Streets Overrides to apply</param> /// <param name="existingElements">A collection of existing elements to which to apply the overrides.</param> /// <param name="identityMatch">A function returning a boolean which indicates whether an element is a match for an override's identity.</param> /// <param name="modifyElement">A function to modify a matched element, returning the modified element.</param> /// <typeparam name="T">The element type this override applies to. Should match the type(s) in the override's context.</typeparam> /// <returns>A collection of elements, including unmodified and modified elements from the supplied collection.</returns> public static List <T> Apply <T>( this IList <VantageStreetsOverride> overrideData, IEnumerable <T> existingElements, Func <T, VantageStreetsIdentity, bool> identityMatch, Func <T, VantageStreetsOverride, T> modifyElement) where T : Element { var resultElements = new List <T>(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); }
/// <summary> /// The Walls function. /// </summary> /// <param name="model">The input model.</param> /// <param name="input">The arguments to the execution.</param> /// <returns>A WallsOutputs instance containing computed results and the model with any new elements.</returns> public static WallsOutputs Execute(Dictionary <string, Model> inputModels, WallsInputs input) { var output = new WallsOutputs(); var defaultWallMaterial = new Material("Wall", Colors.White); if (input.Overrides?.Additions?.Walls != null) { // Get identities for additions var additionCenters = input.Overrides.Additions.Walls.Select(x => (x.Value.CenterLine.PointAt(0.5), x.Id)).Cast <(Vector3 RoughLocation, string Id)>().ToList(); // match edit overrides to addition Ids. var editsByAdditionId = input.Overrides.Walls?.Select(wallEdit => (additionCenters.OrderBy(ac => ac.RoughLocation.DistanceTo(wallEdit.Identity.RoughLocation)).First().Id, wallEdit) ).ToDictionary(x => x.Id, x => x.wallEdit) ?? new Dictionary <string, WallsOverride>(); // match property edit overrides to addition Ids. We have to do a little extra manual cleanup here, // since the property edits are a separate override altogether — the hypar web UI doesn't // automatically remove these from the overrides list. var propertiesByAdditionId = new Dictionary <string, WallPropertiesOverride>(); foreach (var match in input.Overrides.WallProperties?.Select(wallEdit => (additionCenters.OrderBy(ac => ac.RoughLocation.DistanceTo(wallEdit.Identity.RoughLocation)).First().Id, wallEdit) ) ?? new List <(string Id, WallPropertiesOverride)>()) { if (!propertiesByAdditionId.ContainsKey(match.Id)) { propertiesByAdditionId.Add(match.Id, match.wallEdit); } else // non-associated edits don't get deleted automatically, so we might have strays. Find out which one is closer. { var currentEdit = propertiesByAdditionId[match.Id]; var additionCenter = additionCenters.First(ac => ac.Id == match.Id); if (currentEdit.Identity.RoughLocation.DistanceTo(additionCenter.RoughLocation) > match.wallEdit.Identity.RoughLocation.DistanceTo(additionCenter.RoughLocation)) { propertiesByAdditionId[match.Id] = match.wallEdit; } } } // 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 editsByAdditionId.TryGetValue(newWall.Id, out var matchingEdits); wallLine = matchingEdits?.Value?.CenterLine ?? wallLine; propertiesByAdditionId.TryGetValue(newWall.Id, out var matchingProperties); var wallThickness = matchingProperties?.Value.Thickness ?? 0.15; var wallheight = matchingProperties?.Value.Height ?? 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); if (matchingProperties != null) { Identity.AddOverrideIdentity(wall, matchingProperties); } if (matchingEdits != null) { Identity.AddOverrideIdentity(wall, matchingEdits); } // add wall to model output.Model.AddElement(wall); } } return(output); }
/// <summary> /// Generates a building Envelope from a Site boundary. /// </summary> /// <param name="model">The input model.</param> /// <param name="input">The arguments to the execution.</param> /// <returns>A EnvelopeBySiteOutputs instance containing computed results and the model with any new elements.</returns> public static EnvelopeBySiteOutputs Execute(Dictionary <string, Model> inputModels, EnvelopeBySiteInputs input) { // Retrieve site information from incoming models. var sites = new List <Site>(); inputModels.TryGetValue("Site", out var model); if (model == null) { throw new ArgumentException("No Site found."); } sites.AddRange(model.AllElementsOfType <Site>()); sites = sites.OrderByDescending(e => e.Perimeter.Area()).ToList(); var output = new EnvelopeBySiteOutputs(input.BuildingHeight, input.FoundationDepth); // Set input values based on whether we should consider setbacks var siteSetback = input.SiteSetback; var setbackInterval = input.UseSetbacks ? input.SetbackInterval : 0; var setbackDepth = input.UseSetbacks ? input.SetbackDepth : 0; var xy = new Plane((0, 0, 0), (0, 0, 1)); foreach (var site in sites) { var siteCentroid = site.Perimeter.Centroid(); var overridesForSite = input.Overrides?.EnvelopeFootprint?.Where(o => site.Perimeter.Contains(o.Identity.SiteCentroid)) ?? new List <EnvelopeFootprintOverride>(); var perims = site.Perimeter.Offset(siteSetback * -1); if (perims.Count() == 0) { continue; } perims = perims.OrderByDescending(p => p.Area()).ToArray(); var perimeter = perims.First(); if (perimeter.Area() < input.MinimumTierArea) { continue; } var envelopes = new List <Envelope>(); // Create the foundation Envelope. var fndElevation = input.FoundationDepth * -1; var matchingFndOverride = overridesForSite.FirstOrDefault(o => o.Identity.Elevation.ApproximatelyEquals(fndElevation, 1)); var fndPerimeter = matchingFndOverride?.Value?.Perimeter?.Project(xy) ?? perimeter; var extrude = new Elements.Geometry.Solids.Extrude(perimeter, input.FoundationDepth, Vector3.ZAxis, false); var geomRep = new Representation(new List <Elements.Geometry.Solids.SolidOperation>() { extrude }); var fndMatl = new Material("foundation", Palette.Gray, 0.0f, 0.0f); var envMatl = new Material("envelope", Palette.Aqua, 0.0f, 0.0f); var fndXform = new Transform(0.0, 0.0, input.FoundationDepth * -1); var fndEnvelope = new Envelope(fndPerimeter, fndElevation, input.FoundationDepth, Vector3.ZAxis, 0.0, fndXform, fndMatl, geomRep, false, Guid.NewGuid(), "") { Perimeter = fndPerimeter.TransformedPolygon(fndXform), SiteCentroid = siteCentroid }; if (matchingFndOverride != null) { Identity.AddOverrideIdentity(fndEnvelope, matchingFndOverride); } envelopes.Add(fndEnvelope); // Create the Envelope at the location's zero plane. var matchingZeroOverride = overridesForSite.FirstOrDefault(o => o.Identity.Elevation.ApproximatelyEquals(0, 1)); var zeroPerimeter = matchingZeroOverride?.Value?.Perimeter?.Project(xy) ?? perimeter; var tiers = setbackInterval == 0 ? 0 : Math.Floor(input.BuildingHeight / setbackInterval) - 1; var tierHeight = tiers > 0 ? input.BuildingHeight / (tiers + 1) : input.BuildingHeight; extrude = new Elements.Geometry.Solids.Extrude(zeroPerimeter, tierHeight, Vector3.ZAxis, false); geomRep = new Representation(new List <Elements.Geometry.Solids.SolidOperation>() { extrude }); var zeroEnvelope = new Envelope(zeroPerimeter, 0.0, tierHeight, Vector3.ZAxis, 0.0, new Transform(), envMatl, geomRep, false, Guid.NewGuid(), "") { Perimeter = zeroPerimeter, SiteCentroid = siteCentroid }; if (matchingZeroOverride != null) { Identity.AddOverrideIdentity(zeroEnvelope, matchingZeroOverride); perimeter = zeroPerimeter; // this way tiers above, if not overridden, respect the ground floor boundary. } envelopes.Add(zeroEnvelope); // Create the remaining Envelope Elements. var offsFactor = -1; var elevFactor = 1; var totalHeight = 0.0; for (int i = 0; i < tiers; i++) { if (totalHeight + tierHeight > input.BuildingHeight) { break; } var tierElev = tierHeight * elevFactor; var tryPer = perimeter.Offset(setbackDepth * offsFactor); if (tryPer.Count() == 0 || tryPer.First().Area() < input.MinimumTierArea) { break; } tryPer = tryPer.OrderByDescending(p => p.Area()).ToArray(); var matchingTierOverride = overridesForSite.FirstOrDefault(o => o.Identity.Elevation.ApproximatelyEquals(tierElev, 1)); var tierPerimeter = matchingTierOverride?.Value?.Perimeter?.Project(xy) ?? tryPer.First(); extrude = new Elements.Geometry.Solids.Extrude(tierPerimeter, tierHeight, Vector3.ZAxis, false); geomRep = new Representation(new List <Elements.Geometry.Solids.SolidOperation>() { extrude }); var elevationXform = new Transform(0.0, 0.0, tierElev); var tierEnvelope = new Envelope(tierPerimeter, tierElev, tierHeight, Vector3.ZAxis, 0.0, elevationXform, envMatl, geomRep, false, Guid.NewGuid(), "") { Perimeter = tierPerimeter.TransformedPolygon(elevationXform), SiteCentroid = siteCentroid }; if (matchingTierOverride != null) { Identity.AddOverrideIdentity(tierEnvelope, matchingTierOverride); } envelopes.Add(tierEnvelope); offsFactor--; elevFactor++; totalHeight = totalHeight + tierHeight; } envelopes.OrderBy(e => e.Elevation).ToList().ForEach(e => output.Model.AddElement(e)); } return(output); }
/// <summary> /// The SpacePlanningZones function. /// </summary> /// <param name="model">The input model.</param> /// <param name="input">The arguments to the execution.</param> /// <returns>A SpacePlanningZonesOutputs instance containing computed results and the model with any new elements.</returns> public static SpacePlanningZonesOutputs Execute(Dictionary <string, Model> inputModels, SpacePlanningZonesInputs input) { var corridorWidth = input.CorridorWidth; var corridorMat = SpaceBoundary.MaterialDict["Circulation"]; var output = new SpacePlanningZonesOutputs(); var levelsModel = inputModels["Levels"]; var levelVolumes = levelsModel.AllElementsOfType <LevelVolume>(); inputModels.TryGetValue("Floors", out var floorsModel); var hasCore = inputModels.TryGetValue("Core", out var coresModel); var cores = coresModel?.AllElementsOfType <ServiceCore>() ?? new List <ServiceCore>(); var random = new Random(5); var levels = new List <LevelElements>(); var levelMappings = new Dictionary <Guid, (SpaceBoundary boundary, LevelElements level)>(); if (levelVolumes.Count() == 0) { throw new Exception("This function requires LevelVolumes, produced by functions like \"Simple Levels by Envelope\". Try using a different levels function."); } foreach (var lvl in levelVolumes) { if (floorsModel != null) { var floorAtLevel = floorsModel.AllElementsOfType <Floor>().FirstOrDefault(f => Math.Abs(lvl.Transform.Origin.Z - f.Transform.Origin.Z) < (f.Thickness * 1.1)); if (floorAtLevel != null) { lvl.Height -= floorAtLevel.Thickness; var floorFaceOffset = (floorAtLevel.Transform.Origin.Z + floorAtLevel.Thickness) - lvl.Transform.Origin.Z; if (floorFaceOffset > 0.001) { lvl.Transform.Concatenate(new Transform(0, 0, floorFaceOffset)); lvl.Height -= floorFaceOffset; } } } var levelBoundary = new Profile(lvl.Profile.Perimeter, lvl.Profile.Voids, Guid.NewGuid(), null); var coresInBoundary = cores.Where(c => levelBoundary.Contains(c.Centroid)).ToList(); foreach (var core in coresInBoundary) { levelBoundary.Voids.Add(new Polygon(core.Profile.Perimeter.Vertices).Reversed()); levelBoundary.OrientVoids(); } var spaceBoundaries = new List <Element>(); List <Profile> corridorProfiles = new List <Profile>(); if (input.CirculationMode == SpacePlanningZonesInputsCirculationMode.Automatic) { var perimeter = levelBoundary.Perimeter; var perimeterSegments = perimeter.Segments(); IdentifyShortEdges(perimeter, perimeterSegments, out var shortEdges, out var shortEdgeIndices); // Single Loaded Zones var singleLoadedZones = CalculateSingleLoadedZones(input, corridorWidth, perimeterSegments, shortEdgeIndices); GenerateEndZones(input, corridorWidth, lvl, corridorProfiles, perimeterSegments, shortEdges, singleLoadedZones, out var thickenedEnds, out var thickerOffsetProfiles, out var innerOffsetMinusThickenedEnds, out var exclusionRegions); // join single loaded zones to each other (useful in bent-bar case) var allCenterLines = JoinSingleLoaded(singleLoadedZones); // thicken and extend single loaded ThickenAndExtendSingleLoaded(corridorWidth, corridorProfiles, coresInBoundary, thickenedEnds, innerOffsetMinusThickenedEnds, allCenterLines); CorridorsFromCore(corridorWidth, corridorProfiles, levelBoundary, coresInBoundary, innerOffsetMinusThickenedEnds, exclusionRegions); SplitCornersAndGenerateSpaceBoundaries(spaceBoundaries, input, lvl, corridorProfiles, levelBoundary, thickerOffsetProfiles); } else if (input.CirculationMode == SpacePlanningZonesInputsCirculationMode.Manual) { if (input.Corridors != null && input.Corridors.Count > 0) { var corridorProfilesForUnion = new List <Profile>(); foreach (var corridorPolyline in input.Corridors) { if (corridorPolyline == null || corridorPolyline.Polyline == null) { continue; } var corrPgons = corridorPolyline.Polyline.OffsetOnSide(corridorPolyline.Width, corridorPolyline.Flip); corridorProfilesForUnion.AddRange(corrPgons.Select(p => new Profile(p))); } corridorProfiles = Profile.UnionAll(corridorProfilesForUnion); } SplitCornersAndGenerateSpaceBoundaries(spaceBoundaries, input, lvl, corridorProfiles, levelBoundary); } // Construct Level var level = new LevelElements(new List <Element>(), Guid.NewGuid(), lvl.Name); levels.Add(level); // Manual Corridor Splits foreach (var pt in input.AdditionalCorridorLocations) { SplitZones(input, corridorWidth, lvl, spaceBoundaries, corridorProfiles, pt); } // Manual Split Locations foreach (var pt in input.ManualSplitLocations) { SplitZones(input, corridorWidth, lvl, spaceBoundaries, corridorProfiles, pt, false); } foreach (SpaceBoundary b in spaceBoundaries) { levelMappings.Add(b.Id, (b, level)); } corridorProfiles.Select(p => new Floor(p, 0.1, lvl.Transform, corridorMat)).ToList().ForEach(f => level.Elements.Add(f)); } List <SpaceBoundary> SubdividedBoundaries = new List <SpaceBoundary>(); // merge overrides if (input.Overrides != null && input.Overrides.MergeZones != null && input.Overrides.MergeZones.Count > 0) { var spaceBoundaries = levelMappings.Select(kvp => kvp.Value); foreach (var mz in input.Overrides.MergeZones) { var identitiesToMerge = mz.Identities; var matchingSbs = identitiesToMerge.Select(mzI => spaceBoundaries.FirstOrDefault( sb => ((Vector3)sb.boundary.AdditionalProperties["ParentCentroid"]).DistanceTo(mzI.ParentCentroid) < 1.0)).Where(s => s != (null, null)).ToList(); foreach (var msb in matchingSbs) { levelMappings.Remove(msb.boundary.Id); } var sbsByLevel = matchingSbs.GroupBy(sb => sb.level?.Id ?? Guid.Empty); foreach (var lvlGrp in sbsByLevel) { var level = lvlGrp.First().level; var profiles = lvlGrp.Select(sb => sb.boundary.Boundary); var baseobj = lvlGrp.FirstOrDefault(n => n.boundary.Name != null && n.boundary.Name != "unspecified"); if (baseobj == default) { baseobj = lvlGrp.First(); } var baseSB = baseobj.boundary; var union = Profile.UnionAll(profiles); foreach (var newProfile in union) { var rep = baseSB.Representation.SolidOperations.OfType <Extrude>().First(); var newSB = SpaceBoundary.Make(newProfile, baseSB.Name, baseSB.Transform, rep.Height, (Vector3)baseSB.AdditionalProperties["ParentCentroid"], (Vector3)baseSB.AdditionalProperties["ParentCentroid"]); newSB.SetProgram(baseSB.Name); levelMappings.Add(newSB.Id, (newSB, level)); } } } } // assignment overrides if (input.Overrides != null && input.Overrides.ProgramAssignments != null && input.Overrides.ProgramAssignments.Count > 0) { var spaceBoundaries = levelMappings.Select(kvp => kvp.Value).ToList(); // overrides where it is its own parent Console.WriteLine(JsonConvert.SerializeObject(input.Overrides.ProgramAssignments)); foreach (var overrideValue in input.Overrides.ProgramAssignments.Where(o => o.Identity.IndividualCentroid.IsAlmostEqualTo(o.Identity.ParentCentroid))) { var centroid = overrideValue.Identity.ParentCentroid; var matchingSB = spaceBoundaries .OrderBy(sb => ((Vector3)sb.boundary.AdditionalProperties["IndividualCentroid"]).DistanceTo(centroid)) .FirstOrDefault(sb => ((Vector3)sb.boundary.AdditionalProperties["IndividualCentroid"]).DistanceTo(centroid) < 2.0); // var allMatchingSBs = spaceBoundaries // .OrderBy(sb => sb.boundary.Transform.OfPoint(sb.boundary.Boundary.Perimeter.Centroid()).DistanceTo(centroid)) // .Where(sb => sb.boundary.Transform.OfPoint(sb.boundary.Boundary.Perimeter.Centroid()).DistanceTo(centroid) < 2.0); if (matchingSB.boundary != null) { if (overrideValue.Value.Split <= 1) { matchingSB.boundary.SetProgram(overrideValue.Value.ProgramType ?? input.DefaultProgramAssignment); Identity.AddOverrideIdentity(matchingSB.boundary, "Program Assignments", overrideValue.Id, overrideValue.Identity); } else { levelMappings.Remove(matchingSB.boundary.Id); var boundaries = new List <Polygon>(matchingSB.boundary.Boundary.Voids) { matchingSB.boundary.Boundary.Perimeter }; var guideVector = GetDominantAxis(boundaries.SelectMany(b => b.Segments())); var alignmentXform = new Transform(boundaries[0].Start, guideVector, Vector3.ZAxis); var grid = new Grid2d(boundaries, alignmentXform); grid.U.DivideByCount(Math.Max(overrideValue.Value.Split, 1)); foreach (var cell in grid.GetCells().SelectMany(c => c.GetTrimmedCellGeometry())) { var rep = matchingSB.boundary.Representation.SolidOperations.OfType <Extrude>().First(); var newCellSb = SpaceBoundary.Make(cell as Polygon, overrideValue.Value.ProgramType ?? input.DefaultProgramAssignment, matchingSB.boundary.Transform, rep.Height, matchingSB.boundary.AdditionalProperties["ParentCentroid"] as Vector3?); Identity.AddOverrideIdentity(newCellSb, "Program Assignments", overrideValue.Id, overrideValue.Identity); newCellSb.AdditionalProperties["Split"] = overrideValue.Value.Split; SubdividedBoundaries.Add(newCellSb); levelMappings.Add(newCellSb.Id, (newCellSb, matchingSB.level)); } } } } // overrides where it's not its own parent foreach (var overrideValue in input.Overrides.ProgramAssignments.Where(o => !o.Identity.IndividualCentroid.IsAlmostEqualTo(o.Identity.ParentCentroid))) { var matchingCell = SubdividedBoundaries.FirstOrDefault(b => (b.AdditionalProperties["IndividualCentroid"] as Vector3?)?.DistanceTo(overrideValue.Identity.IndividualCentroid) < 0.01); if (matchingCell != null) { Identity.AddOverrideIdentity(matchingCell, "Program Assignments", overrideValue.Id, overrideValue.Identity); matchingCell.SetProgram(overrideValue.Value.ProgramType); } } } foreach (var levelMapping in levelMappings) { levelMapping.Value.level.Elements.Add(levelMapping.Value.boundary); } Dictionary <string, AreaTally> areas = new Dictionary <string, AreaTally>(); foreach (var sb in levels.SelectMany(lev => lev.Elements.OfType <SpaceBoundary>())) { var area = sb.Boundary.Area(); if (sb.Name == null) { continue; } if (!areas.ContainsKey(sb.Name)) { areas[sb.Name] = new AreaTally(sb.Name, sb.Material.Color, area, area, 1, null, Guid.NewGuid(), sb.Name); } else { var existingTally = areas[sb.Name]; existingTally.AchievedArea += area; existingTally.AreaTarget += area; existingTally.DistinctAreaCount++; } output.Model.AddElements(sb.Boundary.ToModelCurves(sb.Transform.Concatenated(new Transform(0, 0, 0.03)))); } output.Model.AddElements(areas.Select(kvp => kvp.Value).OrderByDescending(a => a.AchievedArea)); output.Model.AddElements(levels); return(output); }
/// <summary> /// The SpacePlanningZones function. /// </summary> /// <param name="model">The input model.</param> /// <param name="input">The arguments to the execution.</param> /// <returns>A SpacePlanningZonesOutputs instance containing computed results and the model with any new elements.</returns> public static SpacePlanningZonesOutputs Execute(Dictionary <string, Model> inputModels, SpacePlanningZonesInputs input) { var corridorWidth = input.CorridorWidth; var corridorMat = SpaceBoundary.MaterialDict["Circulation"]; var output = new SpacePlanningZonesOutputs(); var levelsModel = inputModels["Levels"]; var levelVolumes = levelsModel.AllElementsOfType <LevelVolume>(); inputModels.TryGetValue("Floors", out var floorsModel); var coresModel = inputModels["Core"]; var cores = coresModel.AllElementsOfType <ServiceCore>(); var random = new Random(5); var levels = new List <LevelElements>(); var levelMappings = new Dictionary <Guid, (SpaceBoundary boundary, LevelElements level)>(); if (levelVolumes.Count() == 0) { throw new Exception("This function requires LevelVolumes, produced by functions like \"Simple Levels by Envelope\". Try using a different levels function."); } if (cores.Count() == 0) { throw new Exception("No ServiceCore elements were found in the model."); } foreach (var lvl in levelVolumes) { var spaceBoundaries = new List <Element>(); if (floorsModel != null) { var floorAtLevel = floorsModel.AllElementsOfType <Floor>().FirstOrDefault(f => Math.Abs(lvl.Transform.Origin.Z - f.Transform.Origin.Z) < (f.Thickness * 1.1)); if (floorAtLevel != null) { lvl.Height -= floorAtLevel.Thickness; var floorFaceOffset = (floorAtLevel.Transform.Origin.Z + floorAtLevel.Thickness) - lvl.Transform.Origin.Z; if (floorFaceOffset > 0.001) { lvl.Transform.Concatenate(new Transform(0, 0, floorFaceOffset)); lvl.Height -= floorFaceOffset; } } } List <Profile> corridorProfiles = new List <Profile>(); var TOO_SHORT = 9; var levelBoundary = new Profile(lvl.Profile.Perimeter, lvl.Profile.Voids, Guid.NewGuid(), null); var coresInBoundary = cores.Where(c => levelBoundary.Contains(c.Centroid)).ToList(); foreach (var core in coresInBoundary) { levelBoundary.Voids.Add(new Polygon(core.Profile.Perimeter.Vertices).Reversed()); levelBoundary.OrientVoids(); } var perimeter = levelBoundary.Perimeter; var perimeterSegments = perimeter.Segments(); var perimeterAngles = new List <double>(); for (int i = 0; i < perimeter.Vertices.Count; i++) { var nextIndex = (i + 1) % perimeter.Vertices.Count; var prevIndex = (i + perimeter.Vertices.Count - 1) % perimeter.Vertices.Count; var prevVec = perimeter.Vertices[i] - perimeter.Vertices[prevIndex]; var nextVec = perimeter.Vertices[nextIndex] - perimeter.Vertices[i]; var angle = prevVec.PlaneAngleTo(nextVec); perimeterAngles.Add(angle); } var allLengths = perimeterSegments.Select(s => s.Length()); var validLengths = allLengths.Where(l => l > TOO_SHORT)?.OrderBy(l => l); var shortLength = (validLengths?.FirstOrDefault() ?? 35 / 1.2) * 1.2; var longLength = Math.Min(validLengths.SkipLast(1).Last(), 50); var shortEdges = new List <Line>(); var shortEdgeIndices = new List <int>(); for (int i = 0; i < perimeterSegments.Count(); i++) { var start = perimeterAngles[i]; var end = perimeterAngles[(i + 1) % perimeterAngles.Count]; if (start > 80 && start < 100 && end > 80 && end < 100 && perimeterSegments[i].Length() < longLength) { shortEdges.Add(perimeterSegments[i]); shortEdgeIndices.Add(i); } } // Single Loaded Zones var singleLoadedZones = new List <(Polygon hull, Line centerLine)>(); var singleLoadedLengthThreshold = input.OuterBandDepth * 2 + corridorWidth * 2 + 5; // (two offsets, two corridors, and a usable space width) foreach (var sei in shortEdgeIndices) { var ps = perimeterSegments; if (ps[sei].Length() < singleLoadedLengthThreshold) { var legSegments = new[] { ps[(sei + ps.Length - 1) % ps.Length], ps[sei], ps[(sei + 1) % ps.Length] }; var legLength = Math.Min(legSegments[0].Length(), legSegments[2].Length()); legSegments[0] = new Line(ps[sei].Start, ps[sei].Start + legLength * (legSegments[0].Direction() * -1)); legSegments[2] = new Line(ps[sei].End, ps[sei].End + legLength * (legSegments[2].Direction())); var hull = ConvexHull.FromPolylines(legSegments.Select(l => l.ToPolyline(1))); var centerLine = new Line((legSegments[0].Start + legSegments[2].Start) / 2, (legSegments[0].End + legSegments[2].End) / 2); singleLoadedZones.Add((hull, centerLine)); } } var shortEdgesExtended = shortEdges.Select(l => new Line(l.Start - l.Direction() * 0.2, l.End + l.Direction() * 0.2)); var longEdges = perimeterSegments.Except(shortEdges); var shortEdgeDepth = Math.Max(input.DepthAtEnds, input.OuterBandDepth); var longEdgeDepth = input.OuterBandDepth; var perimeterMinusSingleLoaded = new List <Profile>(); perimeterMinusSingleLoaded.AddRange(Profile.Difference(new[] { lvl.Profile }, singleLoadedZones.Select(p => new Profile(p.hull)))); var innerOffset = perimeterMinusSingleLoaded.SelectMany(p => p.Perimeter.Offset(-longEdgeDepth)); var thickerOffsets = shortEdgesExtended.SelectMany(s => s.ToPolyline(1).Offset(shortEdgeDepth, EndType.Butt)).ToList(); var thickerOffsetProfiles = thickerOffsets.Select(o => new Profile(o.Offset(0.01))).ToList(); var endOffsetSegments = thickerOffsets.SelectMany(o => o.Segments()).Where(l => innerOffset.Any(o => o.Contains(l.PointAt(0.5)))); var innerOffsetMinusThicker = innerOffset.SelectMany(i => Polygon.Difference(new[] { i }, thickerOffsets)); var outerband = new Profile(lvl.Profile.Perimeter, innerOffsetMinusThicker.ToList(), Guid.NewGuid(), null); var outerbandLongEdges = Profile.Difference(new List <Profile> { outerband }, thickerOffsetProfiles); var ends = Profile.Intersection(new List <Profile> { outerband }, thickerOffsets.Select(o => new Profile(o)).ToList()); var coreSegments = coresInBoundary.SelectMany(c => c.Profile.Perimeter.Offset((corridorWidth / 2) * 0.999).FirstOrDefault()?.Segments()); var corridorInset = innerOffsetMinusThicker.Select(p => new Profile(p, p.Offset(-corridorWidth), Guid.NewGuid(), "Corridor")); corridorProfiles.AddRange(corridorInset); // join single loaded zones to each other (useful in bent-bar case) var allCenterLines = singleLoadedZones.ToArray(); var distanceThreshold = 10.0; for (int i = 0; i < allCenterLines.Count(); i++) { var crvA = allCenterLines[i].centerLine; for (int j = 0; j < i; j++) { var crvB = allCenterLines[j].centerLine; var doesIntersect = crvA.Intersects(crvB, out var intersection, true, true); Console.WriteLine($"DOES INTERSECT: " + doesIntersect.ToString()); var nearPtA = intersection.ClosestPointOn(crvA); var nearPtB = intersection.ClosestPointOn(crvB); if (nearPtA.DistanceTo(intersection) + nearPtB.DistanceTo(intersection) < distanceThreshold) { if (nearPtA.DistanceTo(crvA.Start) < 0.01) { allCenterLines[i] = (allCenterLines[i].hull, new Line(intersection, crvA.End)); } else { allCenterLines[i] = (allCenterLines[i].hull, new Line(crvA.Start, intersection)); } if (nearPtB.DistanceTo(crvB.Start) < 0.01) { allCenterLines[j] = (allCenterLines[j].hull, new Line(intersection, crvB.End)); } else { allCenterLines[j] = (allCenterLines[j].hull, new Line(crvB.Start, intersection)); } } } } // thicken and extend single loaded foreach (var singleLoadedZone in allCenterLines) { var cl = singleLoadedZone.centerLine; List <Line> centerlines = new List <Line> { cl }; foreach (var core in coresInBoundary) { List <Line> linesRunning = new List <Line>(); foreach (var curve in centerlines) { curve.Trim(core.Profile.Perimeter, out var linesTrimmedByCore); linesRunning.AddRange(linesTrimmedByCore); } centerlines = linesRunning; } cl = centerlines.OrderBy(l => l.Length()).Last(); foreach (var clCandidate in centerlines) { var extended = clCandidate.ExtendTo(innerOffsetMinusThicker.SelectMany(p => p.Segments())).ToPolyline(1); if (extended.Length() == cl.Length() && innerOffsetMinusThicker.Count() > 0) { var end = extended.End; var dist = double.MaxValue; Vector3?runningPt = null; foreach (var boundary in innerOffsetMinusThicker) { var closestDist = end.DistanceTo(boundary, out var pt); if (closestDist < dist) { dist = closestDist; runningPt = pt; } } extended = new Polyline(new[] { extended.Start, extended.End, runningPt.Value }); } //TODO - verify that newly constructed line is contained within building perimeter var thickenedCorridor = extended.Offset(corridorWidth / 2.0, EndType.Square); corridorProfiles.AddRange(Profile.Difference(thickenedCorridor.Select(c => new Profile(c)), thickerOffsets.Select(c => new Profile(c)))); } } foreach (var core in coresInBoundary) { if (singleLoadedZones.Any(z => z.hull.Covers(core.Profile.Perimeter))) { continue; } var boundary = core.Profile.Perimeter.Offset(corridorWidth / 2.0).FirstOrDefault(); var outerOffset = boundary.Offset(corridorWidth).FirstOrDefault(); var coreWrap = new Profile(outerOffset, boundary); var coreWrapWithinFloor = Profile.Intersection(new[] { coreWrap }, new[] { levelBoundary }); } var extendedLines = new List <Line>(); var corridorRegions = new List <Polygon>(); var exclusionRegions = innerOffsetMinusThicker.SelectMany(r => r.Offset(2 * corridorWidth, EndType.Square)); foreach (var enclosedRegion in innerOffsetMinusThicker) { foreach (var segment in coreSegments) { enclosedRegion.Contains(segment.Start, out var startContainment); enclosedRegion.Contains(segment.End, out var endContainment); if (endContainment == Containment.Outside && startContainment == Containment.Outside) { continue; } var extendedSegment = segment.ExtendTo(new Profile(enclosedRegion)); if (extendedSegment.Length() - segment.Length() < 2 * 8) { continue; } extendedLines.Add(extendedSegment); var thickenedCorridor = extendedSegment.ToPolyline(1).Offset(corridorWidth / 2.0, EndType.Butt); var difference = new List <Profile>(); difference = Profile.Difference(corridorProfiles, exclusionRegions.Select(r => new Profile(r))); if (difference.Count > 0 && difference.Sum(d => d.Perimeter.Area()) > 10) { corridorProfiles.AddRange(Profile.Intersection(thickenedCorridor.Select(c => new Profile(c)), new[] { levelBoundary })); } } } var remainingSpaces = Profile.Difference(new[] { levelBoundary }, corridorProfiles); foreach (var remainingSpace in remainingSpaces) { try { if (remainingSpace.Perimeter.Vertices.Any(v => v.DistanceTo(levelBoundary.Perimeter) < 0.1)) { var endCapZones = Profile.Intersection(new[] { remainingSpace }, thickerOffsetProfiles); var linearZones = Profile.Difference(new[] { remainingSpace }, thickerOffsetProfiles); foreach (var linearZone in linearZones) { var segmentsExtended = new List <Polyline>(); foreach (var line in linearZone.Segments()) { if (line.Length() < 2) { continue; } var l = new Line(line.Start - line.Direction() * 0.1, line.End + line.Direction() * 0.1); var extended = l.ExtendTo(linearZone); var endDistance = extended.End.DistanceTo(l.End); var startDistance = extended.Start.DistanceTo(l.Start); var maxExtension = Math.Max(input.OuterBandDepth, input.DepthAtEnds) * 1.6; if (startDistance > 0.1 && startDistance < maxExtension) { var startLine = new Line(extended.Start, line.Start); segmentsExtended.Add(startLine.ToPolyline(1)); } if (endDistance > 0.1 && endDistance < maxExtension) { var endLine = new Line(extended.End, line.End); segmentsExtended.Add(endLine.ToPolyline(1)); } } Console.WriteLine(JsonConvert.SerializeObject(linearZone.Perimeter)); Console.WriteLine(JsonConvert.SerializeObject(linearZone.Voids)); Console.WriteLine(JsonConvert.SerializeObject(segmentsExtended)); var splits = Profile.Split(new[] { linearZone }, segmentsExtended, Vector3.EPSILON); spaceBoundaries.AddRange(splits.Select(s => SpaceBoundary.Make(s, input.DefaultProgramAssignment, lvl.Transform, lvl.Height))); } spaceBoundaries.AddRange(endCapZones.Select(s => SpaceBoundary.Make(s, input.DefaultProgramAssignment, lvl.Transform, lvl.Height))); } else { spaceBoundaries.Add(SpaceBoundary.Make(remainingSpace, input.DefaultProgramAssignment, lvl.Transform, lvl.Height)); } } catch (Exception e) { Console.WriteLine("🚨"); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); spaceBoundaries.Add(SpaceBoundary.Make(remainingSpace, input.DefaultProgramAssignment, lvl.Transform, lvl.Height)); } } var level = new LevelElements(new List <Element>(), Guid.NewGuid(), lvl.Name); levels.Add(level); foreach (var pt in input.AdditionalCorridorLocations) { SplitZones(input, corridorWidth, lvl, spaceBoundaries, corridorProfiles, pt); } foreach (var pt in input.ManualSplitLocations) { SplitZones(input, corridorWidth, lvl, spaceBoundaries, corridorProfiles, pt, false); } foreach (SpaceBoundary b in spaceBoundaries) { levelMappings.Add(b.Id, (b, level)); output.Model.AddElements(b.Boundary.ToModelCurves(b.Transform.Concatenated(new Transform(0, 0, 0.03)))); } corridorProfiles.Select(p => new Floor(p, 0.1, lvl.Transform, corridorMat)).ToList().ForEach(f => level.Elements.Add(f)); } List <SpaceBoundary> SubdividedBoundaries = new List <SpaceBoundary>(); if (input.Overrides != null && input.Overrides.ProgramAssignments.Count > 0) { var spaceBoundaries = levelMappings.Select(kvp => kvp.Value); foreach (var overrideValue in input.Overrides.ProgramAssignments.Where(o => o.Identity.IndividualCentroid.IsAlmostEqualTo(o.Identity.ParentCentroid))) { var centroid = overrideValue.Identity.ParentCentroid; var matchingSB = spaceBoundaries .OrderBy(sb => sb.boundary.Transform.OfPoint(sb.boundary.Boundary.Perimeter.Centroid()).DistanceTo(centroid)) .FirstOrDefault(sb => sb.boundary.Transform.OfPoint(sb.boundary.Boundary.Perimeter.Centroid()).DistanceTo(centroid) < 2.0); var allMatchingSBs = spaceBoundaries .OrderBy(sb => sb.boundary.Transform.OfPoint(sb.boundary.Boundary.Perimeter.Centroid()).DistanceTo(centroid)) .Where(sb => sb.boundary.Transform.OfPoint(sb.boundary.Boundary.Perimeter.Centroid()).DistanceTo(centroid) < 2.0); if (matchingSB.boundary != null) { if (overrideValue.Value.Split <= 1) { matchingSB.boundary.SetProgram(overrideValue.Value.ProgramType ?? input.DefaultProgramAssignment); Identity.AddOverrideIdentity(matchingSB.boundary, "Program Assignments", overrideValue.Id, overrideValue.Identity); } else { levelMappings.Remove(matchingSB.boundary.Id); var boundaries = new List <Polygon>(matchingSB.boundary.Boundary.Voids) { matchingSB.boundary.Boundary.Perimeter }; var guideVector = GetDominantAxis(boundaries.SelectMany(b => b.Segments())); var alignmentXform = new Transform(boundaries[0].Start, guideVector, Vector3.ZAxis); var grid = new Grid2d(boundaries, alignmentXform); grid.U.DivideByCount(Math.Max(overrideValue.Value.Split, 1)); output.Model.AddElements(grid.GetCellSeparators(GridDirection.V, false).Select(c => new ModelCurve(c as Line, new Material("Grey", new Color(0.3, 0.3, 0.3, 1)), matchingSB.boundary.Transform.Concatenated(new Transform(0, 0, 0.02))))); foreach (var cell in grid.GetCells().SelectMany(c => c.GetTrimmedCellGeometry())) { var rep = matchingSB.boundary.Representation.SolidOperations.OfType <Extrude>().First(); var newCellSb = SpaceBoundary.Make(cell as Polygon, overrideValue.Value.ProgramType ?? input.DefaultProgramAssignment, matchingSB.boundary.Transform, rep.Height, matchingSB.boundary.AdditionalProperties["ParentCentroid"] as Vector3?); Identity.AddOverrideIdentity(newCellSb, "Program Assignments", overrideValue.Id, overrideValue.Identity); newCellSb.AdditionalProperties["Split"] = overrideValue.Value.Split; SubdividedBoundaries.Add(newCellSb); levelMappings.Add(newCellSb.Id, (newCellSb, matchingSB.level)); } } } } foreach (var overrideValue in input.Overrides.ProgramAssignments.Where(o => !o.Identity.IndividualCentroid.IsAlmostEqualTo(o.Identity.ParentCentroid))) { var matchingCell = SubdividedBoundaries.FirstOrDefault(b => (b.AdditionalProperties["IndividualCentroid"] as Vector3?)?.DistanceTo(overrideValue.Identity.IndividualCentroid) < 0.01); if (matchingCell != null) { Identity.AddOverrideIdentity(matchingCell, "Program Assignments", overrideValue.Id, overrideValue.Identity); matchingCell.SetProgram(overrideValue.Value.ProgramType); } } } foreach (var levelMapping in levelMappings) { levelMapping.Value.level.Elements.Add(levelMapping.Value.boundary); } Dictionary <string, AreaTally> areas = new Dictionary <string, AreaTally>(); foreach (var sb in levels.SelectMany(lev => lev.Elements.OfType <SpaceBoundary>())) { var area = sb.Boundary.Area(); if (sb.Name == null) { continue; } if (!areas.ContainsKey(sb.Name)) { areas[sb.Name] = new AreaTally(sb.Name, sb.Material.Color, area, area, 1, null, Guid.NewGuid(), sb.Name); } else { var existingTally = areas[sb.Name]; existingTally.AchievedArea += area; existingTally.AreaTarget += area; existingTally.DistinctAreaCount++; } } output.Model.AddElements(areas.Select(kvp => kvp.Value).OrderByDescending(a => a.AchievedArea)); output.Model.AddElements(levels); return(output); }