Ejemplo n.º 1
0
        private static void SplitZones(SpacePlanningZonesInputs input, double corridorWidth, LevelVolume lvl, List <Element> spaceBoundaries, List <Profile> corridorProfiles, Vector3 pt, bool addCorridor = true)
        {
            var containingBoundary = spaceBoundaries.OfType <SpaceBoundary>().FirstOrDefault(b => b.Boundary.Contains(pt));

            if (containingBoundary != null)
            {
                if (input.Overrides != null)
                {
                    var spaceOverrides = input.Overrides.ProgramAssignments.FirstOrDefault(s => s.Identity.IndividualCentroid.IsAlmostEqualTo(containingBoundary.Boundary.Perimeter.Centroid()));
                    if (spaceOverrides != null)
                    {
                        containingBoundary.Name = spaceOverrides.Value.ProgramType;
                    }
                }
                spaceBoundaries.Remove(containingBoundary);
                var perim = containingBoundary.Boundary.Perimeter;
                pt.DistanceTo(perim, out var cp);
                var            line      = new Line(pt, cp);
                var            extension = line.ExtendTo(containingBoundary.Boundary);
                List <Profile> newSbs    = new List <Profile>();
                if (addCorridor)
                {
                    var corridorShape             = extension.ToPolyline(1).Offset(corridorWidth / 2, EndType.Square);
                    var csAsProfiles              = corridorShape.Select(s => new Profile(s));
                    var corridorShapesIntersected = Profile.Intersection(new[] { containingBoundary.Boundary }, csAsProfiles);
                    corridorProfiles.AddRange(corridorShapesIntersected);
                    newSbs = Profile.Difference(new[] { containingBoundary.Boundary }, csAsProfiles);
                }
                else
                {
                    newSbs = Profile.Split(new[] { containingBoundary.Boundary }, new[] { extension.ToPolyline(1) }, Vector3.EPSILON);
                }
                spaceBoundaries.AddRange(newSbs.Select(p => SpaceBoundary.Make(p, containingBoundary.Name, containingBoundary.Transform, lvl.Height)));
            }
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 3
0
        /// <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);
        }