/// <summary> /// The Structure function. /// </summary> /// <param name="model">The model. /// Add elements to the model to have them persisted.</param> /// <param name="input">The arguments to the execution.</param> /// <returns>A StructureOutputs instance containing computed results.</returns> public static StructureOutputs Execute(Dictionary <string, Model> models, StructureInputs input) { var model = new Model(); var warnings = new List <string>(); Elements.Validators.Validator.DisableValidationOnConstruction = true; CellComplex cellComplex = null; Line longestEdge = null; #if DEBUG var sw = new Stopwatch(); sw.Start(); #endif if (models.ContainsKey(BAYS_MODEL_NAME)) { var cellsModel = models[BAYS_MODEL_NAME]; cellComplex = cellsModel.AllElementsOfType <CellComplex>().First(); } else { warnings.Add("Adding the Bays function to your workflow will give you more configurability. We'll use the default configuration for now."); // Create a cell complex with some defaults. if (!models.ContainsKey(LEVELS_MODEL_NAME)) { throw new Exception("If Bays are not supplied Levels are required."); } var levels = models[LEVELS_MODEL_NAME]; var levelVolumes = levels.AllElementsOfType <LevelVolume>().ToList(); if (levelVolumes.Count == 0) { throw new Exception("No LevelVolumes found in your Levels model. Please use a level function that generates LevelVolumes, such as Simple Levels by Envelope"); } // Replicate the old behavior by creating a // grid using the envelope's first level base polygon's longest // edge as the U axis and its perpendicular as the // V axis. var firstLevel = levelVolumes[0]; var firstLevelPerimeter = firstLevel.Profile.Perimeter; longestEdge = firstLevelPerimeter.Segments().OrderBy(s => s.Length()).Last(); var longestEdgeTransform = longestEdge.TransformAt(0.5); var t = new Transform(longestEdge.Start, longestEdgeTransform.XAxis, longestEdge.Direction(), Vector3.ZAxis); var toWorld = new Transform(t); toWorld.Invert(); var bbox = new BBox3(firstLevelPerimeter.Vertices.Select(o => toWorld.OfVector(o)).ToList()); var l = bbox.Max.Y - bbox.Min.Y; var w = bbox.Max.X - bbox.Min.X; var origin = t.OfVector(bbox.Min); var uGrid = new Grid1d(new Line(origin, origin + t.YAxis * l)); uGrid.DivideByFixedLength(DEFAULT_U); var vGrid = new Grid1d(new Line(origin, origin + t.XAxis * w)); vGrid.DivideByFixedLength(DEFAULT_V); var grid = new Grid2d(uGrid, vGrid); var u = grid.U; var v = grid.V; cellComplex = new CellComplex(Guid.NewGuid(), "Temporary Cell Complex"); // Draw level volumes from each level down. for (var i = 1; i < levelVolumes.Count; i++) { var levelVolume = levelVolumes.ElementAt(i); var perimeter = levelVolume.Profile.Perimeter.Offset(-0.5)[0]; var g2d = new Grid2d(perimeter, grid.U, grid.V); var levelElevation = levelVolume.Transform.Origin.Z; var lastLevelVolume = levelVolumes.ElementAt(i - 1); foreach (var cell in g2d.GetCells()) { foreach (var crv in cell.GetTrimmedCellGeometry()) { cellComplex.AddCell((Polygon)crv, lastLevelVolume.Height, levelElevation - lastLevelVolume.Height, g2d.U, g2d.V); if (i == levelVolumes.Count - 1) { cellComplex.AddCell((Polygon)crv, levelVolume.Height, levelElevation, g2d.U, g2d.V); } } } } } #if DEBUG Console.WriteLine($"{sw.ElapsedMilliseconds} ms for getting or creating a cell complex."); sw.Restart(); #endif Vector3 primaryDirection; Vector3 secondaryDirection; IEnumerable <GridLine> gridLines = null; if (models.ContainsKey(GRIDS_MODEL_NAME)) { var gridsModel = models[GRIDS_MODEL_NAME]; gridLines = gridsModel.AllElementsOfType <GridLine>(); // Group by direction. var gridGroups = gridLines.GroupBy(gl => gl.Curve.TransformAt(0).ZAxis).ToList(); primaryDirection = gridGroups[0].Key; secondaryDirection = gridGroups[1].Key; } else { warnings.Add("Adding the Grids function to your workflow will enable you to position and orient the grid. We'll use the default configuration for now with the grid oriented along the longest edge of the structure."); // Define the primary direction from the longest edge of the site. primaryDirection = longestEdge.Direction(); secondaryDirection = longestEdge.TransformAt(0.5).XAxis; } #if DEBUG Console.WriteLine($"{sw.ElapsedMilliseconds} ms for getting or creating grids."); sw.Restart(); #endif var structureMaterial = new Material("Steel", Colors.Gray, 0.5, 0.3); model.AddElement(structureMaterial, false); model.AddElement(BuiltInMaterials.ZAxis, false); var wideFlangeFactory = new WideFlangeProfileFactory(); var shsProfileFactory = new SHSProfileFactory(); var rhsProfileFactory = new RHSProfileFactory(); var columnTypeName = input.ColumnType.ToString(); var columnProfile = GetProfileFromName(columnTypeName, wideFlangeFactory, rhsProfileFactory, shsProfileFactory); var colProfileBounds = columnProfile.Perimeter.Bounds(); var colProfileDepth = colProfileBounds.Max.Y - colProfileBounds.Min.Y; var girderTypeName = input.GirderType.ToString(); Profile girderProfile = null; double girderProfileDepth = 0; if (girderTypeName.StartsWith("LH")) { girderProfileDepth = Units.InchesToMeters(double.Parse(girderTypeName.Split("LH")[1])); } else { girderProfile = GetProfileFromName(girderTypeName, wideFlangeFactory, rhsProfileFactory, shsProfileFactory); var girdProfileBounds = girderProfile.Perimeter.Bounds(); girderProfileDepth = girdProfileBounds.Max.Y - girdProfileBounds.Min.Y; // Set the profile down by half its depth so that // it sits under the slab. girderProfile.Transform(new Transform(new Vector3(0, -girderProfileDepth / 2 - input.SlabThickness))); } Profile beamProfile = null; double beamProfileDepth = 0; var beamTypeName = input.BeamType.ToString(); if (beamTypeName.StartsWith("LH")) { beamProfileDepth = Units.InchesToMeters(double.Parse(beamTypeName.Split("LH")[1])); } else { beamProfile = GetProfileFromName(beamTypeName, wideFlangeFactory, rhsProfileFactory, shsProfileFactory); var beamProfileBounds = beamProfile.Perimeter.Bounds(); beamProfileDepth = beamProfileBounds.Max.Y - beamProfileBounds.Min.Y; // Set the profile down by half its depth so that // it sits under the slab. beamProfile.Transform(new Transform(new Vector3(0, -beamProfileDepth / 2 - input.SlabThickness))); } var edges = cellComplex.GetEdges(); var lowestTierSet = false; var lowestTierElevation = double.MaxValue; var columnDefintions = new Dictionary <(double memberLength, Profile memberProfile), Column>(); var girderDefinitions = new Dictionary <(double memberLength, Profile memberProfile), GeometricElement>(); var beamDefinitions = new Dictionary <(double memberLength, Profile memberProfile), GeometricElement>(); var girderJoistDefinitions = new Dictionary <(double memberLength, double depth), GeometricElement>(); var beamJoistDefinitions = new Dictionary <(double memberLength, double depth), GeometricElement>(); LProfileFactory lProfileFactory; LProfile L8 = null; LProfile L5 = null; LProfile L2 = null; LProfile L3 = null; if (girderProfile == null || beamProfile == null) { lProfileFactory = new LProfileFactory(); L8 = Task.Run(async() => await lProfileFactory.GetProfileByTypeAsync(LProfileType.L8X8X1_2)).Result; L5 = Task.Run(async() => await lProfileFactory.GetProfileByTypeAsync(LProfileType.L5X5X1_2)).Result; L2 = Task.Run(async() => await lProfileFactory.GetProfileByTypeAsync(LProfileType.L2X2X1_8)).Result; L3 = Task.Run(async() => await lProfileFactory.GetProfileByTypeAsync(LProfileType.L3X2X3_16)).Result; } #if DEBUG Console.WriteLine($"{sw.ElapsedMilliseconds} ms for getting all beam and column profiles."); sw.Restart(); #endif var xy = new Plane(Vector3.Origin, Vector3.ZAxis); // Order edges from lowest to highest. foreach (Elements.Spatial.CellComplex.Edge edge in edges.OrderBy(e => Math.Min(cellComplex.GetVertex(e.StartVertexId).Value.Z, cellComplex.GetVertex(e.EndVertexId).Value.Z) )) { var memberLength = edge.Length(cellComplex); var start = cellComplex.GetVertex(edge.StartVertexId).Value; var end = cellComplex.GetVertex(edge.EndVertexId).Value; var direction = (end - start).Unitized(); var warningRepresentation = new Representation(new List <SolidOperation>() { new Extrude(Polygon.Rectangle(0.01, 0.01), 0.01, Vector3.ZAxis, false) }); if (edge.IsVertical(cellComplex)) { // For vertical edges that are not on the grid, we need // a heuristic to determine when we should place a column. // You don't want to place a column all the time because // for non-grid-aligned structures, when you place columns // at every intersection of the envelope and the grid, you // can get columns that are too close together. Instead, we // place a column based on the distance from that column along // a grid line back to a primary grid intersection. If that // distance exceeds the maximum allowable neighbor span, // we place a column. if (!edge.StartsOnGrid(cellComplex)) { var maxDistance = double.MinValue; foreach (var e in edge.GetCells().SelectMany(c => c.GetEdges().Where(e => e != edge && e.StartsOrEndsOnGrid(cellComplex) && e.StartsOrEndsAtThisVertex(edge.StartVertexId, cellComplex) && e.IsHorizontal(cellComplex)))) { var d = e.Length(cellComplex); maxDistance = Math.Max(maxDistance, d); } if (maxDistance < input.MaximumNeighborSpan) { continue; } } var origin = start.IsLowerThan(end) ? start : end; var rotation = Vector3.XAxis.PlaneAngleTo(primaryDirection); Column columnDefinition; if (!columnDefintions.ContainsKey((memberLength, columnProfile))) { columnDefinition = new Column(Vector3.Origin, memberLength, columnProfile, structureMaterial, name: columnProfile.Name) { IsElementDefinition = true }; columnDefinition.Representation.SkipCSGUnion = true; columnDefintions.Add((memberLength, columnProfile), columnDefinition); model.AddElement(columnDefinition, false); } else { columnDefinition = columnDefintions[(memberLength, columnProfile)];