diff --git a/Grids/SketchGrids/.gitignore b/Grids/SketchGrids/.gitignore new file mode 100644 index 00000000..f1d4f671 --- /dev/null +++ b/Grids/SketchGrids/.gitignore @@ -0,0 +1,9 @@ + +bin/ +obj/ +*.glb +output.json +input.json +.vs/ +server/ +test/Generated/ \ No newline at end of file diff --git a/Grids/SketchGrids/README.md b/Grids/SketchGrids/README.md new file mode 100644 index 00000000..4338cc1f --- /dev/null +++ b/Grids/SketchGrids/README.md @@ -0,0 +1,21 @@ + + +# Sketch Grids + +Generate grids automatically from conceptual masses or sketch grid lines. + +|Input Name|Type|Description| +|---|---|---| +|Offset Distance From Conceptual Mass|number|The default grid offset from the conceptual mass.| +|Add Skeleton Grids|boolean|Add grids along conceptual mass skeletons.| + + +
+ +|Output Name|Type|Description| +|---|---|---| + + +
+ +## Additional Information \ No newline at end of file diff --git a/Grids/SketchGrids/SketchGrids.sln b/Grids/SketchGrids/SketchGrids.sln new file mode 100644 index 00000000..31e43d88 --- /dev/null +++ b/Grids/SketchGrids/SketchGrids.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SketchGrids", "src\SketchGrids.csproj", "{EAA286E4-3EE3-4017-945A-18189AE2FDDC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SketchGrids.Dependencies", "dependencies\SketchGrids.Dependencies.csproj", "{C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SketchGrids.Tests", "test\SketchGrids.Tests.csproj", "{6B5FECA2-A2AB-4D74-B89A-A37E33408D91}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Debug|x64.ActiveCfg = Debug|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Debug|x64.Build.0 = Debug|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Debug|x86.ActiveCfg = Debug|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Debug|x86.Build.0 = Debug|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Release|Any CPU.Build.0 = Release|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Release|x64.ActiveCfg = Release|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Release|x64.Build.0 = Release|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Release|x86.ActiveCfg = Release|Any CPU + {EAA286E4-3EE3-4017-945A-18189AE2FDDC}.Release|x86.Build.0 = Release|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Debug|x64.ActiveCfg = Debug|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Debug|x64.Build.0 = Debug|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Debug|x86.ActiveCfg = Debug|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Debug|x86.Build.0 = Debug|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Release|Any CPU.Build.0 = Release|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Release|x64.ActiveCfg = Release|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Release|x64.Build.0 = Release|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Release|x86.ActiveCfg = Release|Any CPU + {C3DC1E0D-A438-4CC9-9DBB-61B81DD1929F}.Release|x86.Build.0 = Release|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Debug|x64.ActiveCfg = Debug|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Debug|x64.Build.0 = Debug|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Debug|x86.ActiveCfg = Debug|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Debug|x86.Build.0 = Debug|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Release|Any CPU.Build.0 = Release|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Release|x64.ActiveCfg = Release|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Release|x64.Build.0 = Release|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Release|x86.ActiveCfg = Release|Any CPU + {6B5FECA2-A2AB-4D74-B89A-A37E33408D91}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Grids/SketchGrids/dependencies/ConceptualMass.g.cs b/Grids/SketchGrids/dependencies/ConceptualMass.g.cs new file mode 100644 index 00000000..fa933a87 --- /dev/null +++ b/Grids/SketchGrids/dependencies/ConceptualMass.g.cs @@ -0,0 +1,69 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + /// Represents an early stage building massing volume. Its boundary typically represents the nominal edge of slab, and may be articulated further by subsequent functions. + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class ConceptualMass : Envelope + { + [JsonConstructor] + public ConceptualMass(IList @skeleton, string @primaryUseCategory, System.Guid? @building, IList @levelIds, Transform @localCoordinateSystem, Profile @profile, double @elevation, double @height, Vector3 @direction, double @rotation, IList @floorToFloorHeights, Transform @transform, Material @material, Representation @representation, bool @isElementDefinition, System.Guid @id, string @name) + : base(profile, elevation, height, direction, rotation, floorToFloorHeights, transform, material, representation, isElementDefinition, id, name) + { + this.Skeleton = @skeleton; + this.PrimaryUseCategory = @primaryUseCategory; + this.Building = @building; + this.LevelIds = @levelIds; + this.LocalCoordinateSystem = @localCoordinateSystem; + } + + + // Empty constructor + public ConceptualMass() + : base() + { + } + + /// A collection of lines which indicate a "centerline" of the mass. + [JsonProperty("Skeleton", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList Skeleton { get; set; } + + /// The primary programmatic use for this mass, e.g. Residential, Office, Parking, Retail. Some levels or spaces within the mass may have other uses. + [JsonProperty("Primary Use Category", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string PrimaryUseCategory { get; set; } + + /// The building this mass belongs to, if any. + [JsonProperty("Building", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Guid? Building { get; set; } + + /// The ids of the levels this conceptual mass contains + [JsonProperty("Level Ids", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList LevelIds { get; set; } + + /// A transform representing the local coordinate system for this mass + [JsonProperty("Local Coordinate System", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Transform LocalCoordinateSystem { get; set; } + + + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/Envelope.g.cs b/Grids/SketchGrids/dependencies/Envelope.g.cs new file mode 100644 index 00000000..a5a575de --- /dev/null +++ b/Grids/SketchGrids/dependencies/Envelope.g.cs @@ -0,0 +1,74 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + /// Represents one part of a building enclosure. + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class Envelope : GeometricElement + { + [JsonConstructor] + public Envelope(Profile @profile, double @elevation, double @height, Vector3 @direction, double @rotation, IList @floorToFloorHeights, Transform @transform = null, Material @material = null, Representation @representation = null, bool @isElementDefinition = false, System.Guid @id = default, string @name = null) + : base(transform, material, representation, isElementDefinition, id, name) + { + this.Profile = @profile; + this.Elevation = @elevation; + this.Height = @height; + this.Direction = @direction; + this.Rotation = @rotation; + this.FloorToFloorHeights = @floorToFloorHeights; + } + + + // Empty constructor + public Envelope() + : base() + { + } + + /// The profile to extrude. + [JsonProperty("Profile", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Profile Profile { get; set; } + + /// The elevation of the envelope. + [JsonProperty("Elevation", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double Elevation { get; set; } + + /// The height of the envelope. + [JsonProperty("Height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double Height { get; set; } + + /// The direction in which to extrude. + [JsonProperty("Direction", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Vector3 Direction { get; set; } + + /// The rotation of the envelope, in degrees. + [JsonProperty("Rotation", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double Rotation { get; set; } + + /// An optional list of floor-to-floor heights for this envelope. If provided, levels can be generated from these floor-to-floor heights. + [JsonProperty("Floor To Floor Heights", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList FloorToFloorHeights { get; set; } + + + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/GridLinesOverride.g.cs b/Grids/SketchGrids/dependencies/GridLinesOverride.g.cs new file mode 100644 index 00000000..2a8fa601 --- /dev/null +++ b/Grids/SketchGrids/dependencies/GridLinesOverride.g.cs @@ -0,0 +1,153 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace SketchGrids +{ + /// + /// Override metadata for GridLinesOverride + /// + public partial class GridLinesOverride : IOverride + { + public static string Name = "Grid Lines"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.GridLine]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + public static class GridLinesOverrideExtensions + { + /// + /// Apply Grid Lines edit overrides to a collection of existing elements + /// + /// The Grid Lines Overrides to apply + /// A collection of existing elements to which to apply the overrides. + /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to modify a matched element, returning the modified element. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including unmodified and modified elements from the supplied collection. + public static List Apply( + this IList overrideData, + IEnumerable existingElements, + Func identityMatch, + Func modifyElement) where T : Element + { + var resultElements = new List(existingElements); + if (overrideData != null) + { + foreach (var overrideValue in overrideData) + { + // Assuming there will only be one match per identity, find the first element that matches. + var matchingElement = existingElements.FirstOrDefault(e => identityMatch(e, overrideValue.Identity)); + // if we found a match, + if (matchingElement != null) + { + // remove the old matching element + resultElements.Remove(matchingElement); + // apply the modification function to it + var modifiedElement = modifyElement(matchingElement, overrideValue); + // set the identity + Identity.AddOverrideIdentity(modifiedElement, overrideValue); + //and re-add it to the collection + resultElements.Add(modifiedElement); + } + } + } + return resultElements; + } + + /// + /// Apply Grid Lines edit overrides to a collection of existing elements + /// + /// A collection of existing elements to which to apply the overrides. + /// The Grid Lines Overrides to apply — typically `input.Overrides.GridLines` + /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to modify a matched element, returning the modified element. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including unmodified and modified elements from the supplied collection. + public static void ApplyOverrides( + this List existingElements, + IList overrideData, + Func identityMatch, + Func modifyElement + ) where T : Element + { + var updatedElements = overrideData.Apply(existingElements, identityMatch, modifyElement); + existingElements.Clear(); + existingElements.AddRange(updatedElements); + } + + /// + /// Create elements from add/removeoverrides, and apply any edits. + /// + /// The collection of edit overrides (Overrides.GridLines) + /// The collection of add overrides (Overrides.Additions.GridLines) + /// The collection of remove overrides (Overrides.Removals.GridLines) /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to create a new element, returning the created element. + /// A function to modify a matched element, returning the modified element. + /// An optional collection of existing elements to which to apply any edit overrides, or remove if remove overrides are found. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including new, unmodified, and modified elements from the supplied collection. + public static List CreateElements( + this IList edits, + IList additions, + IList removals, Func createElement, + Func identityMatch, + Func modifyElement, + IEnumerable existingElements = null + ) where T : Element + { + List resultElements = existingElements == null ? new List() : new List(existingElements); + if (removals != null) + { + foreach (var removedElement in removals) + { + var elementToRemove = resultElements.FirstOrDefault(e => identityMatch(e, removedElement.Identity)); + if (elementToRemove != null) + { + resultElements.Remove(elementToRemove); + } + } + } if (additions != null) + { + foreach (var addedElement in additions) + { + var elementToAdd = createElement(addedElement); + resultElements.Add(elementToAdd); + Identity.AddOverrideIdentity(elementToAdd, addedElement); + } + } + if (edits != null) + { + foreach (var editedElement in edits) + { + var elementToEdit = resultElements.FirstOrDefault(e => identityMatch(e, editedElement.Identity)); + if (elementToEdit != null) + { + resultElements.Remove(elementToEdit); + var newElement = modifyElement(elementToEdit, editedElement); + resultElements.Add(newElement); + Identity.AddOverrideIdentity(newElement, editedElement); + } + } + } + return resultElements; + } + + } + + +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/GridLinesOverrideAddition.g.cs b/Grids/SketchGrids/dependencies/GridLinesOverrideAddition.g.cs new file mode 100644 index 00000000..6f1b6f8b --- /dev/null +++ b/Grids/SketchGrids/dependencies/GridLinesOverrideAddition.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace SketchGrids +{ + /// + /// Override metadata for GridLinesOverrideAddition + /// + public partial class GridLinesOverrideAddition : IOverride + { + public static string Name = "Grid Lines Addition"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.GridLine]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/GridLinesOverrideRemoval.g.cs b/Grids/SketchGrids/dependencies/GridLinesOverrideRemoval.g.cs new file mode 100644 index 00000000..46e7c6f9 --- /dev/null +++ b/Grids/SketchGrids/dependencies/GridLinesOverrideRemoval.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace SketchGrids +{ + /// + /// Override metadata for GridLinesOverrideRemoval + /// + public partial class GridLinesOverrideRemoval : IOverride + { + public static string Name = "Grid Lines Removal"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.GridLine]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/LocalEdge.cs b/Grids/SketchGrids/dependencies/LocalEdge.cs new file mode 100644 index 00000000..b76f0b7d --- /dev/null +++ b/Grids/SketchGrids/dependencies/LocalEdge.cs @@ -0,0 +1,83 @@ +using System; + +namespace SketchGrids +{ + /// + /// Provides graph edge info + /// + public class LocalEdge + { + [Flags] + private enum VisitDirections + { + None, + Straight, + Opposite + } + + /// + /// Creates a new instance of Edge class + /// + /// The index of the first vertex + /// The index of the second vertex + public LocalEdge(int vertexIndex1, int vertexIndex2) + { + visitDirections = VisitDirections.None; + VertexIndex1 = vertexIndex1; + VertexIndex2 = vertexIndex2; + } + + public int VertexIndex1 { get; } + public int VertexIndex2 { get; } + + /// + /// Added record that edge was visited from the vertex index + /// + /// The index of the vertex where the edge is visited from + public void MarkAsVisited(int vertexIndex) + { + if (vertexIndex == VertexIndex1) + { + visitDirections |= VisitDirections.Straight; + } + else if (vertexIndex == VertexIndex2) + { + visitDirections |= VisitDirections.Opposite; + } + } + + /// + /// Returns if this edge is between input vertex indeces + /// + /// The index of the first vertex + /// The index of the second vertex + /// Returns True if edge is between input vertex indeces + public bool IsBetweenVertices(int vertexIndex1, int vertexIndex2) + { + return (VertexIndex1 == vertexIndex1 && VertexIndex2 == vertexIndex2) || + (VertexIndex1 == vertexIndex2 && VertexIndex2 == vertexIndex1); + } + + /// + /// Gets if the edge was visited from the vertex + /// + /// The index of the vertex where the edge is visited from + /// Returns True if the edge was visited from the vertex + public bool IsVisitedFromVertex(int vertexIndex) + { + if (VertexIndex1 == vertexIndex) + { + return visitDirections.HasFlag(VisitDirections.Straight); + } + + if (VertexIndex2 == vertexIndex) + { + return visitDirections.HasFlag(VisitDirections.Opposite); + } + + return false; + } + + private VisitDirections visitDirections; + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/SketchGrids.Dependencies.csproj b/Grids/SketchGrids/dependencies/SketchGrids.Dependencies.csproj new file mode 100644 index 00000000..91caf5c0 --- /dev/null +++ b/Grids/SketchGrids/dependencies/SketchGrids.Dependencies.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + + + + + + \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/SketchGridsInputs.g.cs b/Grids/SketchGrids/dependencies/SketchGridsInputs.g.cs new file mode 100644 index 00000000..3a05b48e --- /dev/null +++ b/Grids/SketchGrids/dependencies/SketchGridsInputs.g.cs @@ -0,0 +1,350 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Validators; +using Elements.Serialization.JSON; +using Hypar.Functions; +using Hypar.Functions.Execution; +using Hypar.Functions.Execution.AWS; +using Hypar.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace SketchGrids +{ + #pragma warning disable // Disable all warnings + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public class SketchGridsInputs : ArgsBase + + { + [Newtonsoft.Json.JsonConstructor] + + public SketchGridsInputs(double @offsetDistanceFromConceptualMass, bool @addSkeletonGrids, Overrides @overrides, Dictionary modelInputKeys, string gltfKey, string elementsKey, string ifcKey): + base(modelInputKeys, gltfKey, elementsKey, ifcKey) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @offsetDistanceFromConceptualMass, @addSkeletonGrids, @overrides}); + } + + this.OffsetDistanceFromConceptualMass = @offsetDistanceFromConceptualMass; + this.AddSkeletonGrids = @addSkeletonGrids; + this.Overrides = @overrides ?? this.Overrides; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + /// The default grid offset from the conceptual mass. + [Newtonsoft.Json.JsonProperty("Offset Distance From Conceptual Mass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double OffsetDistanceFromConceptualMass { get; set; } = 1.5D; + + /// Add grids along conceptual mass skeletons. + [Newtonsoft.Json.JsonProperty("Add Skeleton Grids", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool AddSkeletonGrids { get; set; } = false; + + [Newtonsoft.Json.JsonProperty("overrides", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Overrides Overrides { get; set; } = new Overrides(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class Overrides + + { + public Overrides() { } + + [Newtonsoft.Json.JsonConstructor] + public Overrides(OverrideAdditions @additions, OverrideRemovals @removals, IList @gridLines) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @additions, @removals, @gridLines}); + } + + this.Additions = @additions ?? this.Additions; + this.Removals = @removals ?? this.Removals; + this.GridLines = @gridLines ?? this.GridLines; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Additions", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public OverrideAdditions Additions { get; set; } = new OverrideAdditions(); + + [Newtonsoft.Json.JsonProperty("Removals", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public OverrideRemovals Removals { get; set; } = new OverrideRemovals(); + + [Newtonsoft.Json.JsonProperty("Grid Lines", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList GridLines { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class OverrideAdditions + + { + public OverrideAdditions() { } + + [Newtonsoft.Json.JsonConstructor] + public OverrideAdditions(IList @gridLines) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @gridLines}); + } + + this.GridLines = @gridLines ?? this.GridLines; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Grid Lines", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList GridLines { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class OverrideRemovals + + { + public OverrideRemovals() { } + + [Newtonsoft.Json.JsonConstructor] + public OverrideRemovals(IList @gridLines) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @gridLines}); + } + + this.GridLines = @gridLines ?? this.GridLines; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Grid Lines", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList GridLines { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class GridLinesOverride + + { + [Newtonsoft.Json.JsonConstructor] + public GridLinesOverride(string @id, GridLinesIdentity @identity, GridLinesValue @value) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity, @value}); + } + + this.Id = @id; + this.Identity = @identity; + this.Value = @value; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public GridLinesIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public GridLinesValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class GridLinesOverrideAddition + + { + [Newtonsoft.Json.JsonConstructor] + public GridLinesOverrideAddition(string @id, GridLinesIdentity @identity, GridLinesOverrideAdditionValue @value) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity, @value}); + } + + this.Id = @id; + this.Identity = @identity; + this.Value = @value; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public GridLinesIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public GridLinesOverrideAdditionValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class GridLinesOverrideRemoval + + { + [Newtonsoft.Json.JsonConstructor] + public GridLinesOverrideRemoval(string @id, GridLinesIdentity @identity) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity}); + } + + this.Id = @id; + this.Identity = @identity; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public GridLinesIdentity Identity { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class GridLinesIdentity + + { + [Newtonsoft.Json.JsonConstructor] + public GridLinesIdentity(Line @curve) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @curve}); + } + + this.Curve = @curve; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Curve", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Curve { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class GridLinesValue + + { + [Newtonsoft.Json.JsonConstructor] + public GridLinesValue(Line @curve, string @name) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @curve, @name}); + } + + this.Curve = @curve; + this.Name = @name; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Curve", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Curve { get; set; } + + /// The name of the grid line. + [Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Name { get; set; } = "XXX"; + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class GridLinesOverrideAdditionValue + + { + [Newtonsoft.Json.JsonConstructor] + public GridLinesOverrideAdditionValue(Line @curve, string @name) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @curve, @name}); + } + + this.Curve = @curve; + this.Name = @name; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Curve", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Curve { get; set; } + + /// The name of the grid line. + [Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Name { get; set; } = "XXX"; + + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/dependencies/SketchGridsOutputs.g.cs b/Grids/SketchGrids/dependencies/SketchGridsOutputs.g.cs new file mode 100644 index 00000000..159d236e --- /dev/null +++ b/Grids/SketchGrids/dependencies/SketchGridsOutputs.g.cs @@ -0,0 +1,29 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Hypar.Functions; +using Hypar.Functions.Execution; +using Hypar.Functions.Execution.AWS; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Collections.Generic; + +namespace SketchGrids +{ + public class SketchGridsOutputs: SystemResults + { + + /// + /// Construct a SketchGridsOutputs with default inputs. + /// This should be used for testing only. + /// + public SketchGridsOutputs() : base() + { + } + + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/hypar.json b/Grids/SketchGrids/hypar.json new file mode 100644 index 00000000..ab810eef --- /dev/null +++ b/Grids/SketchGrids/hypar.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://hypar.io/Schemas/Function.json", + "id": "7b53fd3e-d94a-4333-9127-045722dddbd8", + "name": "Sketch Grids", + "description": "Generate grids automatically from conceptual masses or sketch grid lines.", + "language": "C#", + "input_schema": { + "type": "object", + "properties": { + "Offset Distance From Conceptual Mass": { + "description": "The default grid offset from the conceptual mass.", + "type": "number", + "default": 1.5, + "$hyparUnitType": "length" + }, + "Add Skeleton Grids": { + "description": "Add grids along conceptual mass skeletons.", + "type": "boolean", + "default": false + } + } + }, + "overrides": { + "Grid Lines": { + "context": "[*discriminator=Elements.GridLine]", + "identity": { + "Curve": { + "$ref": "https://hypar.io/Schemas/Geometry/Line.json" + } + }, + "schema": { + "Curve": { + "$ref": "https://hypar.io/Schemas/Geometry/Line.json", + "$hyparAllowIntersection": true, + "$hyparContinueDrawing": false + }, + "Name": { + "description": "The name of the grid line.", + "type": "string", + "default": "XXX" + } + }, + "behaviors": { + "add": { + "schema": { + "Curve": { + "$ref": "https://hypar.io/Schemas/Geometry/Line.json", + "$hyparAllowIntersection": true, + "$hyparContinueDrawing": false + }, + "Name": { + "description": "The name of the grid line.", + "type": "string", + "default": "XXX" + } + } + }, + "remove": true + } + } + }, + "outputs": [], + "model_dependencies": [ + { + "name": "Conceptual Mass", + "autohide": false, + "optional": true + } + ], + "element_types": [ + "https://schemas.hypar.io/ConceptualMass.json" + ], + "model_output": "Grids", + "repository_url": "https://github.com/hypar-io/function", + "last_updated": "0001-01-01T00:00:00", + "cli_version": "1.1.1" +} \ No newline at end of file diff --git a/Grids/SketchGrids/src/Function.g.cs b/Grids/SketchGrids/src/Function.g.cs new file mode 100644 index 00000000..3d88030c --- /dev/null +++ b/Grids/SketchGrids/src/Function.g.cs @@ -0,0 +1,73 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Amazon.Lambda.Core; +using Hypar.Functions.Execution; +using Hypar.Functions.Execution.AWS; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] +namespace SketchGrids +{ + public class Function + { + // Cache the model store for use by subsequent + // executions of this lambda. + private UrlModelStore store; + + public async Task Handler(SketchGridsInputs args) + { + // Preload dependencies (if they exist), + // so that they are available during model deserialization. + + var sw = System.Diagnostics.Stopwatch.StartNew(); + var asmLocation = this.GetType().Assembly.Location; + var asmDir = Path.GetDirectoryName(asmLocation); + + // Explicitly load the dependencies project, it might have types + // that aren't used in the function but are necessary for correct + // deserialization. + var asmName = Path.GetFileNameWithoutExtension(asmLocation); + var depPath = Path.Combine(asmDir, $"{asmName}.Dependencies.dll"); + if(File.Exists(depPath)) + { + Console.WriteLine($"Loading dependencies assembly from: {depPath}..."); + Assembly.LoadFrom(depPath); + Console.WriteLine("Dependencies assembly loaded."); + } + + // Load all reference assemblies. + Console.WriteLine($"Loading all referenced assemblies."); + foreach (var asm in this.GetType().Assembly.GetReferencedAssemblies()) + { + try + { + Console.WriteLine($"Assembly Name: {asm.FullName}"); + Assembly.Load(asm); + } + catch (Exception e) + { + Console.WriteLine($"Failed to load {asm.FullName}"); + Console.WriteLine(e.Message); + } + } + sw.Stop(); + Console.WriteLine($"Time to load assemblies: {sw.Elapsed.TotalSeconds})"); + + if(this.store == null) + { + this.store = new UrlModelStore(); + } + + + var l = new InvocationWrapper (store, SketchGrids.Execute); + var output = await l.InvokeAsync(args); + return output; + } + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/src/SketchGrids.cs b/Grids/SketchGrids/src/SketchGrids.cs new file mode 100644 index 00000000..8e152629 --- /dev/null +++ b/Grids/SketchGrids/src/SketchGrids.cs @@ -0,0 +1,207 @@ +using Elements; +using Elements.Geometry; +using Elements.Search; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SketchGrids +{ + public static class SketchGrids + { + /// + /// The SketchGrids function. + /// + /// The input model. + /// The arguments to the execution. + /// A SketchGridsOutputs instance containing computed results and the model with any new elements. + public static SketchGridsOutputs Execute(Dictionary inputModels, SketchGridsInputs input) + { + var output = new SketchGridsOutputs(); + var edgeDisplaySettings = new EdgeDisplaySettings() + { + WidthMode = EdgeDisplayWidthMode.ScreenUnits, + LineWidth = 1, + DashSize = 40, + // DashMode = EdgeDisplayDashMode.ScreenUnits + }; + + var gridMaterial = new Material("Grid Line", Colors.Darkgray) + { + EdgeDisplaySettings = edgeDisplaySettings + }; + + var gridLines = new List(); + + if (inputModels.ContainsKey("Conceptual Mass")) + { + CreateGridLinesForConceptualMass(inputModels, input, gridMaterial, gridLines); + } + + gridLines = input.Overrides.GridLines.CreateElements(input.Overrides.Additions.GridLines, input.Overrides.Removals.GridLines, + (add) => + { + var gridLine = new GridLine() + { + Curve = add.Value.Curve, + Material = gridMaterial, + Name = add.Value.Name ?? "XXX" + }; + gridLine.AdditionalProperties["Creation Id"] = add.Id; + return gridLine; + }, (gl, identity) => + { + return identity.Curve != null && ((Line)gl.Curve).IsAlmostEqualTo(identity.Curve, false); + }, (gl, @override) => + { + gl.Curve = @override.Value.Curve; + gl.Name = @override.Value.Name ?? "XXX"; + return gl; + }, gridLines); + + output.Model.AddElements(gridLines); + + var texts = new List<(Vector3 location, Vector3 facingDirection, Vector3 lineDirection, string text, Color? color)>(); + foreach (var gridLine in gridLines) + { + var t = gridLine.GetCircleTransform(); + texts.Add(((t.Origin.X, t.Origin.Y), Vector3.ZAxis, Vector3.XAxis, $"{gridLine.Name ?? "XXX"}", Colors.Black)); + } + output.Model.AddElement(new ModelText(texts, FontSize.PT60, scale: 50)); + + return output; + } + + private static void CreateGridLinesForConceptualMass(Dictionary inputModels, + SketchGridsInputs input, + Material gridMaterial, + List gridLines) + { + var conceptualMasses = inputModels["Conceptual Mass"].AllElementsOfType(); + + var gridIndex = 0; + var allVoids = new List(); + + foreach (var conceptualMass in conceptualMasses) + { + var offsetHull = conceptualMass.Profile.Perimeter.Offset(1)[0]; + + conceptualMass.UpdateRepresentations(); + conceptualMass.UpdateBoundsAndComputeSolid(); + + if (conceptualMass.Skeleton != null) + { + // Create grids for the skeleton + if (input.AddSkeletonGrids) + { + foreach (var edge in conceptualMass.Skeleton) + { + var newGridLine = edge.ExtendTo(offsetHull); + CheckAndCreateGridline(newGridLine, gridLines, ref gridIndex, gridMaterial, input); + } + } + + var allPerimeters = new List(); + + if (conceptualMass.Profile != null && conceptualMass.Profile.Voids != null) + { + allPerimeters.Add(conceptualMass.Profile.Perimeter); + foreach (var profileVoid in conceptualMass.Profile.Voids) + { + allVoids.Add(profileVoid); + } + } + + // Offset perimeters to the inside. + foreach (var cutPerimeter in allPerimeters) + { + foreach (var segment in cutPerimeter.Offset(-input.OffsetDistanceFromConceptualMass)[0].Segments()) + { + var newGridLine = segment.ExtendTo(offsetHull); + CheckAndCreateGridline(newGridLine, gridLines, ref gridIndex, gridMaterial, input); + } + } + + // TODO: Are there ever holes in a skeleton mass? + // Offset holes to the outside. + // foreach (var cutVoid in allVoids) + // { + // foreach (var segment in cutVoid.Offset(-structuralOffset)[0].Segments()) + // { + // var newGridLine = segment.ExtendTo(offsetHull); + // CheckAndCreateGridline(newGridLine, gridLines, ref gridIndex, gridMaterial); + // } + // } + } + else + { + foreach (var segment in conceptualMass.Profile.Perimeter.Offset(-input.OffsetDistanceFromConceptualMass)[0].Segments()) + { + var newGridLine = segment.ExtendTo(offsetHull); + CheckAndCreateGridline(newGridLine, gridLines, ref gridIndex, gridMaterial, input); + } + + if (conceptualMass.Profile.Voids != null) + { + allVoids.AddRange(conceptualMass.Profile.Voids); + } + } + } + + // We process the voids last so that we have all grids + // created from the faces of the conceptual masses to respond + // to. This will create the shortest spanning edge grids for holes. + foreach (var profileVoid in allVoids) + { + var holeGridLines = new List(); + var offsetHull = profileVoid.Offset(input.OffsetDistanceFromConceptualMass)[0]; + + foreach (var segment in offsetHull.Segments()) + { + holeGridLines.Add(segment); + } + + foreach (var holeGridLine in holeGridLines) + { + CheckAndCreateGridline(holeGridLine, gridLines, ref gridIndex, gridMaterial, input); + } + } + } + + private static void CheckAndCreateGridline(Line newGridLine, + List gridLines, + ref int gridIndex, + Material gridMaterial, + SketchGridsInputs input) + { + if (gridLines.Any(gl => (gl.Curve as Line).IsAlmostEqualTo(newGridLine, false))) + { + return; + } + + GridLinesOverride matchingEditOverride = null; + if (input.Overrides?.GridLines != null) + { + matchingEditOverride = input.Overrides.GridLines.FirstOrDefault((glo) => glo.Identity.Curve != null && glo.Identity.Curve.IsAlmostEqualTo(newGridLine, false)); + if (matchingEditOverride != null) + { + newGridLine = matchingEditOverride.Value.Curve; + } + } + + var gl = new GridLine() + { + Curve = newGridLine, + Name = gridIndex.ToString(), + Material = gridMaterial + }; + gridLines.Add(gl); + gridIndex++; + + if (matchingEditOverride != null) + { + Identity.AddOverrideIdentity(gl, matchingEditOverride); + } + } + } +} diff --git a/Grids/SketchGrids/src/SketchGrids.csproj b/Grids/SketchGrids/src/SketchGrids.csproj new file mode 100644 index 00000000..4c86d152 --- /dev/null +++ b/Grids/SketchGrids/src/SketchGrids.csproj @@ -0,0 +1,9 @@ + + + + + + + net6.0 + + \ No newline at end of file diff --git a/Grids/SketchGrids/src/VectorEqualityComparer.cs b/Grids/SketchGrids/src/VectorEqualityComparer.cs new file mode 100644 index 00000000..9028988c --- /dev/null +++ b/Grids/SketchGrids/src/VectorEqualityComparer.cs @@ -0,0 +1,29 @@ +using Elements.Geometry; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace SketchGrids +{ + public class VectorEqualityComparer : IEqualityComparer + { + public bool Equals([AllowNull] Vector3 x, [AllowNull] Vector3 y) + { + return x.IsAlmostEqualTo(y); + } + + public int GetHashCode([DisallowNull] Vector3 obj) + { + // Stolen from the vector 3 class, but using + // less precision to enforce matches. + unchecked + { + int hash = 17; + hash = hash * 23 + Math.Round(obj.X, 5).GetHashCode(); + hash = hash * 23 + Math.Round(obj.Y, 5).GetHashCode(); + hash = hash * 23 + Math.Round(obj.Z, 5).GetHashCode(); + return hash; + } + } + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/test/FunctionTest.g.cs b/Grids/SketchGrids/test/FunctionTest.g.cs new file mode 100644 index 00000000..de92b88c --- /dev/null +++ b/Grids/SketchGrids/test/FunctionTest.g.cs @@ -0,0 +1,24 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Xunit; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using Xunit.Abstractions; +using System; +using System.Collections.Generic; + +namespace SketchGrids.Tests +{ + public class FunctionTests + { + private readonly ITestOutputHelper output; + + public FunctionTests(ITestOutputHelper output) + { + this.output = output; + } + } +} \ No newline at end of file diff --git a/Grids/SketchGrids/test/SketchGrids.Tests.csproj b/Grids/SketchGrids/test/SketchGrids.Tests.csproj new file mode 100644 index 00000000..f2953633 --- /dev/null +++ b/Grids/SketchGrids/test/SketchGrids.Tests.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + false + + + + + + + + + + + \ No newline at end of file diff --git a/Structure/StructureByEnvelope/dependencies/Camera.g.cs b/Structure/StructureByEnvelope/dependencies/Camera.g.cs index f8f7fb70..292f181e 100644 --- a/Structure/StructureByEnvelope/dependencies/Camera.g.cs +++ b/Structure/StructureByEnvelope/dependencies/Camera.g.cs @@ -19,12 +19,12 @@ namespace Elements { - #pragma warning disable // Disable all warnings +#pragma warning disable // Disable all warnings /// Represents camera settings [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class Camera + public partial class Camera { [JsonConstructor] public Camera(Vector3 @angle, CameraNamedPosition? @namedPosition, CameraProjection? @projection) @@ -32,28 +32,28 @@ public Camera(Vector3 @angle, CameraNamedPosition? @namedPosition, CameraProject this.Angle = @angle; this.NamedPosition = @namedPosition; this.Projection = @projection; - } - - + } + + // Empty constructor public Camera() { } - + /// A unit vector in model coordinates indicating which direction the camera is pointing. [JsonProperty("angle", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public Vector3 Angle { get; set; } - + /// Camera positions, viewing from this direction to the opposite direction. Do not set angle if setting this. [JsonProperty("named_position", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public CameraNamedPosition? NamedPosition { get; set; } - + /// How the camera collapses the 3d scene into a 2d image [JsonProperty("projection", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public CameraProjection? Projection { get; set; } - - + + } } \ No newline at end of file diff --git a/Structure/StructureByEnvelope/dependencies/LevelVolume.g.cs b/Structure/StructureByEnvelope/dependencies/LevelVolume.g.cs index d437122c..32be11bf 100644 --- a/Structure/StructureByEnvelope/dependencies/LevelVolume.g.cs +++ b/Structure/StructureByEnvelope/dependencies/LevelVolume.g.cs @@ -19,7 +19,7 @@ namespace Elements { - #pragma warning disable // Disable all warnings +#pragma warning disable // Disable all warnings /// Describes the volume of occupiable space between a level and the next level above it. [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] @@ -38,47 +38,47 @@ public LevelVolume(Profile @profile, double @height, double @area, string @build this.Mass = @mass; this.PlanView = @planView; this.Profiles = @profiles; - } - - + } + + // Empty constructor public LevelVolume() : base() { } - + /// The profile of the level Volume [JsonProperty("Profile", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public Profile Profile { get; set; } - + /// The floor-to-floor height of this level [JsonProperty("Height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double Height { get; set; } - + /// The area of the level's profile. [JsonProperty("Area", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double Area { get; set; } - + /// The name of the building or mass this level belongs to (optional) [JsonProperty("Building Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string BuildingName { get; set; } - + /// The Level this volume was created from. [JsonProperty("Level", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Guid? Level { get; set; } - + /// The Conceptual Mass this volume was created from. [JsonProperty("Mass", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Guid? Mass { get; set; } - + /// The default plan view for this level [JsonProperty("Plan View", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Guid? PlanView { get; set; } - + /// Multiple profiles used for a collection of volumes [JsonProperty("Profiles", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public IList Profiles { get; set; } - - + + } } \ No newline at end of file diff --git a/Structure/StructureByEnvelope/dependencies/ViewScope.g.cs b/Structure/StructureByEnvelope/dependencies/ViewScope.g.cs index aeff1468..a0404035 100644 --- a/Structure/StructureByEnvelope/dependencies/ViewScope.g.cs +++ b/Structure/StructureByEnvelope/dependencies/ViewScope.g.cs @@ -19,65 +19,65 @@ namespace Elements { - #pragma warning disable // Disable all warnings +#pragma warning disable // Disable all warnings - /// Represents a preset view attached to an element. - [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] - public partial class ViewScope : Element - { - [JsonConstructor] - public ViewScope(BBox3 @boundingBox, Camera @camera, bool? @lockRotation, bool? @clipWithBoundingBox, ViewScopeClippingBehavior? @clippingBehavior, bool? @modal, System.Collections.Generic.IDictionary @functionVisibility, IList @actions, System.Guid @id = default, string @name = null) - : base(id, name) - { - this.BoundingBox = @boundingBox; - this.Camera = @camera; - this.LockRotation = @lockRotation; - this.ClipWithBoundingBox = @clipWithBoundingBox; - this.ClippingBehavior = @clippingBehavior; - this.Modal = @modal; - this.FunctionVisibility = @functionVisibility; - this.Actions = @actions; - } - - - // Empty constructor - public ViewScope() - : base() + /// Represents a preset view attached to an element. + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class ViewScope : Element { + [JsonConstructor] + public ViewScope(BBox3 @boundingBox, Camera @camera, bool? @lockRotation, bool? @clipWithBoundingBox, ViewScopeClippingBehavior? @clippingBehavior, bool? @modal, System.Collections.Generic.IDictionary @functionVisibility, IList @actions, System.Guid @id = default, string @name = null) + : base(id, name) + { + this.BoundingBox = @boundingBox; + this.Camera = @camera; + this.LockRotation = @lockRotation; + this.ClipWithBoundingBox = @clipWithBoundingBox; + this.ClippingBehavior = @clippingBehavior; + this.Modal = @modal; + this.FunctionVisibility = @functionVisibility; + this.Actions = @actions; + } + + + // Empty constructor + public ViewScope() + : base() + { + } + + /// The "focus" extent for this view. + [JsonProperty("Bounding Box", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public BBox3 BoundingBox { get; set; } + + /// The camera to use for this view. + [JsonProperty("Camera", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Camera Camera { get; set; } + + /// Whether this scope should lock view rotation. True to lock, False to unlock, and null to leave unchanged. + [JsonProperty("Lock Rotation", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? LockRotation { get; set; } + + /// Whether this scope should clip to the specified bounding box. If false, it only zooms to the bounding box without clipping. + [JsonProperty("Clip With Bounding Box", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? ClipWithBoundingBox { get; set; } + + /// When clipping with this box, which sides should actually do the clipping? If null, all six sides will be assumed. Front is assumed to be the side facing -Y, Top is the side facing +Z + [JsonProperty("Clipping Behavior", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public ViewScopeClippingBehavior? ClippingBehavior { get; set; } + + /// If true, when the user exits this scope, they'll return to their previous camera settings. + [JsonProperty("Modal", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Modal { get; set; } + + [JsonProperty("Function Visibility", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.IDictionary FunctionVisibility { get; set; } + + [JsonProperty("Actions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList Actions { get; set; } + + } - - /// The "focus" extent for this view. - [JsonProperty("Bounding Box", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public BBox3 BoundingBox { get; set; } - - /// The camera to use for this view. - [JsonProperty("Camera", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public Camera Camera { get; set; } - - /// Whether this scope should lock view rotation. True to lock, False to unlock, and null to leave unchanged. - [JsonProperty("Lock Rotation", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool? LockRotation { get; set; } - - /// Whether this scope should clip to the specified bounding box. If false, it only zooms to the bounding box without clipping. - [JsonProperty("Clip With Bounding Box", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool? ClipWithBoundingBox { get; set; } - - /// When clipping with this box, which sides should actually do the clipping? If null, all six sides will be assumed. Front is assumed to be the side facing -Y, Top is the side facing +Z - [JsonProperty("Clipping Behavior", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public ViewScopeClippingBehavior? ClippingBehavior { get; set; } - - /// If true, when the user exits this scope, they'll return to their previous camera settings. - [JsonProperty("Modal", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool? Modal { get; set; } - - [JsonProperty("Function Visibility", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.IDictionary FunctionVisibility { get; set; } - - [JsonProperty("Actions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public IList Actions { get; set; } - - - } } \ No newline at end of file diff --git a/Structure/StructureByEnvelope/src/Structure.cs b/Structure/StructureByEnvelope/src/Structure.cs index 019c14df..3745be10 100644 --- a/Structure/StructureByEnvelope/src/Structure.cs +++ b/Structure/StructureByEnvelope/src/Structure.cs @@ -10,6 +10,7 @@ using Elements.Spatial.CellComplex; using System.Diagnostics; using System.IO; +using Elements.Search; namespace Structure { @@ -440,7 +441,18 @@ public static StructureOutputs Execute(Dictionary models, Structu var length = longestCellEdge.Length(); var remainder = length % input.BeamSpacing; - var beamGrid = new Grid1d(longestCellEdge); + var cellPts = new List(); + foreach (var cellPt in cell.GetEdges().Select(e => cellComplex.GetVertex(e.StartVertexId).Value)) + { + cellPts.Add(cellPt.ClosestPointOn(longestCellEdge, true)); + } + + // We've got all the cell points projected to the line, but we + // want to find the maximum spanning line, so we sort by distance + // relative to a point far away from the start of the line. + cellPts.Sort(new DistanceComparer(longestCellEdge.Start - d * 10000)); + + var beamGrid = new Grid1d(new Line(cellPts.First(), cellPts.Last())); beamGrid.DivideByApproximateLength(input.BeamSpacing, EvenDivisionMode.RoundDown); var cellSeparators = beamGrid.GetCellSeparators(); @@ -452,30 +464,46 @@ public static StructureOutputs Execute(Dictionary models, Structu continue; } + // Project a line across the polygon and trim it + // with the polygon. var pt = cellSeparators[i]; var t = new Transform(pt, d, Vector3.ZAxis); - var r = new Ray(t.Origin, t.YAxis); - foreach (var s in segments) + var testLine = new Line(t.Origin, t.Origin + t.YAxis * 10000); + var trimmedLines = testLine.Trim(p, out _, true); + + foreach (var l in trimmedLines) { - if (s == longestCellEdge) + var beamLength = l.Length(); + if (beamLength < 1) { continue; } - if (r.Intersects(s, out Vector3 xsect)) + GeometricElement beamDefinition; + if (beamProfile != null) { - if (t.Origin.DistanceTo(xsect) < 1) - { - continue; - } - - var l = new Line(t.Origin, xsect); - var beamLength = l.Length(); - - GeometricElement beamDefinition; - if (beamProfile != null) + FindOrCreateStructuralFramingDefinition(beamLength, beamProfile, structureMaterial, beamDefinitions, model, out beamDefinition); + } + else + { + // Create a joist + if (!beamJoistDefinitions.ContainsKey((beamLength, beamProfileDepth))) { - FindOrCreateStructuralFramingDefinition(beamLength, beamProfile, structureMaterial, beamDefinitions, model, out beamDefinition); + // Beam definitions are defined along the X axis + var cl = new Line(Vector3.Origin, new Vector3(beamLength, 0)); + // if (beamLength < beamProfileDepth) + // { + // continue; + // } + + var cellCount = (int)Math.Ceiling((beamLength - Units.InchesToMeters(24)) / beamProfileDepth); + beamDefinition = new Joist(cl, L3, L3, L2, beamProfileDepth, cellCount, Units.InchesToMeters(2.5), Units.InchesToMeters(12), structureMaterial) + { + IsElementDefinition = true + }; + beamDefinition.Representation.SkipCSGUnion = true; + beamJoistDefinitions.Add((beamLength, beamProfileDepth), beamDefinition); + model.AddElement(beamDefinition, false); } else { @@ -516,6 +544,21 @@ public static StructureOutputs Execute(Dictionary models, Structu model.AddElement(modelCurve, false); } } + var beamDir = l.Direction(); + var instanceTransform = new Transform(l.Start, beamDir, Vector3.ZAxis); + var beamInstance = beamDefinition.CreateInstance(instanceTransform, $"{beamDefinition.Name}"); + beamInstance.AdditionalProperties.Add(CELL_ID_PROPERTY_NAME, cell.Id); + model.AddElement(beamInstance, false); + var planDirection = beamDir.IsAlmostEqualTo(Vector3.ZAxis) ? Vector3.XAxis : beamDir.Project(xy).Unitized(); + beamInstance.AdditionalProperties.Add("LabelConfiguration", new LabelConfiguration(new Color(1, 1, 1, 0), Vector3.Origin, null, null, planDirection)); + if (beamDefinition is Beam beam) + { + model.AddElement(new ModelCurve(beam.Curve.Transformed(instanceTransform), BuiltInMaterials.ZAxis), false); + } + else if (beamDefinition is Joist joist) + { + model.AddElement(new ModelCurve(joist.Curve.Transformed(instanceTransform), BuiltInMaterials.ZAxis), false); + } } } } @@ -531,6 +574,7 @@ public static StructureOutputs Execute(Dictionary models, Structu output.Model = model; output.Warnings = warnings; return output; + } private static ModelCurve CreateModelCurve(GeometricElement sf, Transform t)