From 036ea8fc1d8847cb356f6081532bede31c9b3dc5 Mon Sep 17 00:00:00 2001 From: Nadiia Volyk Date: Mon, 26 Jun 2023 15:50:30 +0300 Subject: [PATCH] Add overrides for interior partitions --- .../InteriorPartitionTypesOverride.g.cs | 153 ++++++++ ...nteriorPartitionTypesOverrideAddition.g.cs | 32 ++ ...InteriorPartitionTypesOverrideRemoval.g.cs | 32 ++ .../InteriorPartitionsInputs.g.cs | 327 +++++++++++++++++- .../dependencies/WallCandidate.cs | 32 ++ LayoutFunctions/InteriorPartitions/hypar.json | 47 +++ .../src/InteriorPartitions.cs | 238 ++++++++++++- .../InteriorPartitions/test/OverridesTest.cs | 105 ++++++ .../test/OverridesTestData/inputs1.json | 86 +++++ .../test/OverridesTestData/inputs2.json | 50 +++ .../test/OverridesTestData/inputs3.json | 158 +++++++++ .../test/OverridesTestData/inputs4.json | 86 +++++ .../test/OverridesTestData/inputs5.json | 68 ++++ .../test/OverridesTestData/inputs6.json | 124 +++++++ .../LayoutFunctionCommon/WallGeneration.cs | 116 ++++++- 15 files changed, 1637 insertions(+), 17 deletions(-) create mode 100644 LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverride.g.cs create mode 100644 LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideAddition.g.cs create mode 100644 LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideRemoval.g.cs create mode 100644 LayoutFunctions/InteriorPartitions/dependencies/WallCandidate.cs create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTest.cs create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs1.json create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs2.json create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs3.json create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs4.json create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs5.json create mode 100644 LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs6.json diff --git a/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverride.g.cs b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverride.g.cs new file mode 100644 index 00000000..4675dbe7 --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverride.g.cs @@ -0,0 +1,153 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace InteriorPartitions +{ + /// + /// Override metadata for InteriorPartitionTypesOverride + /// + public partial class InteriorPartitionTypesOverride : IOverride + { + public static string Name = "Interior Partition Types"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.WallCandidate]"; + 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 InteriorPartitionTypesOverrideExtensions + { + /// + /// Apply Interior Partition Types edit overrides to a collection of existing elements + /// + /// The Interior Partition Types 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 Interior Partition Types edit overrides to a collection of existing elements + /// + /// A collection of existing elements to which to apply the overrides. + /// The Interior Partition Types Overrides to apply — typically `input.Overrides.InteriorPartitionTypes` + /// 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.InteriorPartitionTypes) + /// The collection of add overrides (Overrides.Additions.InteriorPartitionTypes) + /// The collection of remove overrides (Overrides.Removals.InteriorPartitionTypes) /// 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/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideAddition.g.cs b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideAddition.g.cs new file mode 100644 index 00000000..81649a77 --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideAddition.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace InteriorPartitions +{ + /// + /// Override metadata for InteriorPartitionTypesOverrideAddition + /// + public partial class InteriorPartitionTypesOverrideAddition : IOverride + { + public static string Name = "Interior Partition Types Addition"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.WallCandidate]"; + 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/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideRemoval.g.cs b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideRemoval.g.cs new file mode 100644 index 00000000..6c3d844a --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionTypesOverrideRemoval.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace InteriorPartitions +{ + /// + /// Override metadata for InteriorPartitionTypesOverrideRemoval + /// + public partial class InteriorPartitionTypesOverrideRemoval : IOverride + { + public static string Name = "Interior Partition Types Removal"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.WallCandidate]"; + 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/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionsInputs.g.cs b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionsInputs.g.cs index b0054114..aac66e4a 100644 --- a/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionsInputs.g.cs +++ b/LayoutFunctions/InteriorPartitions/dependencies/InteriorPartitionsInputs.g.cs @@ -29,15 +29,16 @@ public class InteriorPartitionsInputs : S3Args { [Newtonsoft.Json.JsonConstructor] - public InteriorPartitionsInputs(string bucketName, string uploadsBucket, Dictionary modelInputKeys, string gltfKey, string elementsKey, string ifcKey): + public InteriorPartitionsInputs(Overrides @overrides, string bucketName, string uploadsBucket, Dictionary modelInputKeys, string gltfKey, string elementsKey, string ifcKey): base(bucketName, uploadsBucket, modelInputKeys, gltfKey, elementsKey, ifcKey) { var validator = Validator.Instance.GetFirstValidatorForType(); if(validator != null) { - validator.PreConstruct(new object[]{ }); + validator.PreConstruct(new object[]{ @overrides}); } + this.Overrides = @overrides ?? this.Overrides; if(validator != null) { @@ -45,5 +46,327 @@ public InteriorPartitionsInputs(string bucketName, string uploadsBucket, Diction } } + [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 @interiorPartitionTypes) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @additions, @removals, @interiorPartitionTypes}); + } + + this.Additions = @additions ?? this.Additions; + this.Removals = @removals ?? this.Removals; + this.InteriorPartitionTypes = @interiorPartitionTypes ?? this.InteriorPartitionTypes; + + 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("Interior Partition Types", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList InteriorPartitionTypes { 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 @interiorPartitionTypes) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @interiorPartitionTypes}); + } + + this.InteriorPartitionTypes = @interiorPartitionTypes ?? this.InteriorPartitionTypes; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Interior Partition Types", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList InteriorPartitionTypes { 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 @interiorPartitionTypes) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @interiorPartitionTypes}); + } + + this.InteriorPartitionTypes = @interiorPartitionTypes ?? this.InteriorPartitionTypes; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Interior Partition Types", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList InteriorPartitionTypes { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class InteriorPartitionTypesOverride + + { + [Newtonsoft.Json.JsonConstructor] + public InteriorPartitionTypesOverride(string @id, InteriorPartitionTypesIdentity @identity, InteriorPartitionTypesValue @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 InteriorPartitionTypesIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public InteriorPartitionTypesValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class InteriorPartitionTypesOverrideAddition + + { + [Newtonsoft.Json.JsonConstructor] + public InteriorPartitionTypesOverrideAddition(string @id, InteriorPartitionTypesIdentity @identity, InteriorPartitionTypesOverrideAdditionValue @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 InteriorPartitionTypesIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public InteriorPartitionTypesOverrideAdditionValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class InteriorPartitionTypesOverrideRemoval + + { + [Newtonsoft.Json.JsonConstructor] + public InteriorPartitionTypesOverrideRemoval(string @id, InteriorPartitionTypesIdentity @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 InteriorPartitionTypesIdentity Identity { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class InteriorPartitionTypesIdentity + + { + [Newtonsoft.Json.JsonConstructor] + public InteriorPartitionTypesIdentity(Line @line, string @addId) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @line, @addId}); + } + + this.Line = @line; + this.AddId = @addId; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Line", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Line { get; set; } + + [Newtonsoft.Json.JsonProperty("AddId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AddId { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class InteriorPartitionTypesValue + + { + [Newtonsoft.Json.JsonConstructor] + public InteriorPartitionTypesValue(InteriorPartitionTypesValueType @type, Line @line) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @type, @line}); + } + + this.Type = @type; + this.Line = @line; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public InteriorPartitionTypesValueType Type { get; set; } = InteriorPartitionTypesValueType.Solid; + + [Newtonsoft.Json.JsonProperty("Line", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Line { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class InteriorPartitionTypesOverrideAdditionValue + + { + [Newtonsoft.Json.JsonConstructor] + public InteriorPartitionTypesOverrideAdditionValue(InteriorPartitionTypesOverrideAdditionValueType @type, Line @line) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @type, @line}); + } + + this.Type = @type; + this.Line = @line; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public InteriorPartitionTypesOverrideAdditionValueType Type { get; set; } = InteriorPartitionTypesOverrideAdditionValueType.Solid; + + [Newtonsoft.Json.JsonProperty("Line", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Line { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public enum InteriorPartitionTypesValueType + { + [System.Runtime.Serialization.EnumMember(Value = @"Solid")] + Solid = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"Partition")] + Partition = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"Glass")] + Glass = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public enum InteriorPartitionTypesOverrideAdditionValueType + { + [System.Runtime.Serialization.EnumMember(Value = @"Solid")] + Solid = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"Partition")] + Partition = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"Glass")] + Glass = 2, + } } \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/dependencies/WallCandidate.cs b/LayoutFunctions/InteriorPartitions/dependencies/WallCandidate.cs new file mode 100644 index 00000000..bc859509 --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/dependencies/WallCandidate.cs @@ -0,0 +1,32 @@ +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 +{ + public partial class WallCandidate + { + public double Height { get; set; } + + public Transform LevelTransform { get; set; } + + public string AddId { get; set; } + + public WallCandidate(Line @line, string @type, double height, Transform levelTransform, IList @spaceAdjacencies = null, System.Guid @id = default, string @name = null) + : this(@line, @type, @spaceAdjacencies, id, name) + { + Height = height; + LevelTransform = levelTransform; + } + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/hypar.json b/LayoutFunctions/InteriorPartitions/hypar.json index 0725ca82..7169942b 100644 --- a/LayoutFunctions/InteriorPartitions/hypar.json +++ b/LayoutFunctions/InteriorPartitions/hypar.json @@ -82,6 +82,53 @@ "optional": true } ], + "overrides": { + "Interior Partition Types": { + "context": "[*discriminator=Elements.WallCandidate]", + "identity": { + "Line": { + "$ref": "https://schemas.hypar.io/Line.json" + }, + "AddId": { + "type": "string" + } + }, + "paradigm": "edit", + "behaviors": { + "add": { + "schema": { + "Type": { + "type": "string", + "default": "Solid", + "enum": [ + "Solid", + "Partition", + "Glass" + ] + }, + "Line": { + "$ref": "https://schemas.hypar.io/Line.json" + } + } + }, + "remove": true + }, + "schema": { + "Type": { + "type": "string", + "default": "Solid", + "enum": [ + "Solid", + "Partition", + "Glass" + ] + }, + "Line": { + "$ref": "https://schemas.hypar.io/Line.json", + } + } + } + }, "outputs": [], "element_types": [ "https://schemas.hypar.io/WallCandidate.json" diff --git a/LayoutFunctions/InteriorPartitions/src/InteriorPartitions.cs b/LayoutFunctions/InteriorPartitions/src/InteriorPartitions.cs index 68f5b96b..d5caabec 100644 --- a/LayoutFunctions/InteriorPartitions/src/InteriorPartitions.cs +++ b/LayoutFunctions/InteriorPartitions/src/InteriorPartitions.cs @@ -3,11 +3,16 @@ using System.Collections.Generic; using System.Linq; using LayoutFunctionCommon; +using System; +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("InteriorPartitions.Tests")] namespace InteriorPartitions { public static class InteriorPartitions { + private static double defaultHeight = 3; + /// /// The InteriorPartitions function. /// @@ -40,22 +45,243 @@ public static InteriorPartitionsOutputs Execute(Dictionary inputM { interiorPartitionCandidates.AddRange(mdModel?.AllElementsOfType()); } - } var output = new InteriorPartitionsOutputs(); + var wallCandidates = CreateWallCandidates(input, interiorPartitionCandidates); + output.Model.AddElements(wallCandidates); + var wallCandidatesGroups = wallCandidates.GroupBy(w => (w.LevelTransform, w.Height)); + foreach (var wallCandidatesGroup in wallCandidatesGroups) + { + WallGeneration.GenerateWalls(output.Model, wallCandidatesGroup.Select(w => (w.Line, w.Type, w.Id)), wallCandidatesGroup.Key.Height, wallCandidatesGroup.Key.LevelTransform); + } + return output; + } + + internal static List CreateWallCandidates(InteriorPartitionsInputs input, List interiorPartitionCandidates) + { // TODO: don't assume one height for all walls on a level — pass height through deduplication. var levelGroups = interiorPartitionCandidates.Where(c => c.WallCandidateLines.Count > 0).GroupBy(c => c.LevelTransform); + var wallCandidates = new List(); + var userAddedWallLinesCandidates = new List(); + if (input.Overrides?.InteriorPartitionTypes != null) + { + userAddedWallLinesCandidates = input.Overrides.InteriorPartitionTypes.CreateElementsFromEdits( + input.Overrides.Additions.InteriorPartitionTypes, + (add) => new WallCandidate(add.Value.Line, add.Value.Type.ToString(), defaultHeight, new Transform()) { AddId = add.Id }, + (wall, ident) => MatchIdentityWallCandidate(wall, ident), + (wall, edit) => UpdateWallCandidate(wall, edit) + ); + } + foreach (var levelGroup in levelGroups) { var candidates = WallGeneration.DeduplicateWallLines(levelGroup.ToList()); - var height = levelGroup.OrderBy(l => l.Height).FirstOrDefault()?.Height ?? 3; - var wallCandidates = candidates.Select(c => new WallCandidate(c.line.TransformedLine(levelGroup.Key), c.type, new List())); - output.Model.AddElements(wallCandidates); - WallGeneration.GenerateWalls(output.Model, wallCandidates.Select(w => (w.Line, w.Type, w.Id)), height, levelGroup.Key); + var height = levelGroup.OrderBy(l => l.Height).FirstOrDefault()?.Height ?? defaultHeight; + var levelWallCandidates = candidates.Select(c => + new WallCandidate(c.line.TransformedLine(levelGroup.Key), + c.type, + height, + levelGroup.Key, + new List())); + if (input.Overrides?.InteriorPartitionTypes != null) + { + levelWallCandidates = UpdateLevelWallCandidates(levelWallCandidates, input.Overrides.InteriorPartitionTypes, input.Overrides.Removals.InteriorPartitionTypes); + } + + var splittedCandidates = WallGeneration.SplitOverlappingWallCandidates( + levelWallCandidates.Select(w => (w.Line, w.Type)), + userAddedWallLinesCandidates.Select(w => (w.Line.TransformedLine(w.LevelTransform), w.Type))); + var splittedWallCandidates = splittedCandidates + .Select(c => new WallCandidate(c.line, c.type, height, levelGroup.Key, new List())) + .ToList(); + + wallCandidates.AddRange(splittedWallCandidates); } + AttachOverrides(input.Overrides.InteriorPartitionTypes, wallCandidates, input.Overrides.Additions.InteriorPartitionTypes); + RemoveWallCandidates(input.Overrides.Removals.InteriorPartitionTypes, wallCandidates); - return output; + return wallCandidates; + } + + private static bool MatchIdentityWallCandidate(WallCandidate wallCandidate, InteriorPartitionTypesIdentity ident) + { + var isLinesEqual = ident.Line.IsAlmostEqualTo(wallCandidate.Line, false, 0.1); + return ident.AddId?.Equals(wallCandidate.AddId?.ToString()) == true && isLinesEqual; + } + + private static WallCandidate UpdateWallCandidate(WallCandidate wallCandidate, InteriorPartitionTypesOverride edit) + { + wallCandidate.Line = edit.Value.Line ?? wallCandidate.Line; + wallCandidate.Type = edit.Value.Type.ToString(); + return wallCandidate; + } + + public static List CreateElementsFromEdits( + this IList edits, + IList additions, + Func createElement, + Func identityMatch, + Func modifyElement) + { + var resultElements = new List(); + 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); + } + else + { + var newElement = new WallCandidate(editedElement.Value.Line, editedElement.Value.Type.ToString(), defaultHeight, new Transform(), null); + resultElements.Add(newElement); + Identity.AddOverrideIdentity(newElement, editedElement); + } + } + } + return resultElements; + } + + private static List UpdateLevelWallCandidates( + IEnumerable levelWallCandidates, + IList edits, + IList removals) + { + var resultElements = new List(levelWallCandidates); + if (removals != null) + { + foreach (var removedElement in removals) + { + var elementToRemove = resultElements.FirstOrDefault(e => removedElement.Identity.Line.IsAlmostEqualTo(e.Line, false, 0.1)); + if (elementToRemove != null) + { + resultElements.Remove(elementToRemove); + } + } + } + if (edits != null) + { + foreach (var editedElement in edits) + { + WallCandidate overlappingWallCandidate = null; + var identityLine = editedElement.Identity.Line; + foreach (var wallCandidate in resultElements) + { + if (!wallCandidate.Line.IsCollinear(identityLine)) + { + continue; + } + + // check if secondLine lies inside firstLine + if (!Line.PointOnLine(identityLine.Start, wallCandidate.Line.Start, wallCandidate.Line.End, true) + || !Line.PointOnLine(identityLine.End, wallCandidate.Line.Start, wallCandidate.Line.End, true)) + { + continue; + } + + overlappingWallCandidate = wallCandidate; + break; + } + + if (overlappingWallCandidate != null) + { + var overlappingLine = overlappingWallCandidate.Line; + var vectors = new List() { overlappingLine.Start, overlappingLine.End, identityLine.Start, identityLine.End }; + var direction = overlappingLine.Direction(); + var orderedVectors = vectors.OrderBy(v => (v - overlappingLine.Start).Dot(direction)).ToList(); + + resultElements.Remove(overlappingWallCandidate); + if (!orderedVectors[0].IsAlmostEqualTo(orderedVectors[1])) + { + resultElements.Add(new WallCandidate(new Line(orderedVectors[0], orderedVectors[1]), overlappingWallCandidate.Type, overlappingWallCandidate.Height, overlappingWallCandidate.LevelTransform)); + } + + resultElements.Add(new WallCandidate(editedElement.Value.Line, editedElement.Value.Type.ToString(), overlappingWallCandidate.Height, overlappingWallCandidate.LevelTransform)); + + if (!orderedVectors[2].IsAlmostEqualTo(orderedVectors[3])) + { + resultElements.Add(new WallCandidate(new Line(orderedVectors[2], orderedVectors[3]), overlappingWallCandidate.Type, overlappingWallCandidate.Height, overlappingWallCandidate.LevelTransform)); + } + } + } + } + return resultElements; + } + + public static void AttachOverrides(this IList overrideData, IEnumerable existingElements, IList additions) + { + if (overrideData != null) + { + foreach (var overrideValue in overrideData) + { + var matchingElement = existingElements.FirstOrDefault(e => overrideValue.Value.Line.IsAlmostEqualTo(e.Line, false, 0.01)); + if (matchingElement != null) + { + matchingElement.Type = overrideValue.Value.Type.ToString(); + Identity.AddOverrideIdentity(matchingElement, overrideValue); + + var addOverride = additions?.FirstOrDefault(a => a.Id.Equals(overrideValue.Identity.AddId)); + if (addOverride != null) + { + Identity.AddOverrideIdentity(matchingElement, addOverride); + SetAddIdForContainedElements(existingElements, overrideValue.Value.Line, addOverride); + } + } + } + } + if (additions != null) + { + var elementsWithoutIdentities = existingElements.Where(e => !e.AdditionalProperties.ContainsKey("associatedIdentities")); + foreach (var addedElement in additions) + { + var matchingElement = elementsWithoutIdentities.FirstOrDefault(e => addedElement.Value.Line.IsAlmostEqualTo(e.Line, false, 0.01)); + if (matchingElement != null) + { + Identity.AddOverrideIdentity(matchingElement, addedElement); + } + SetAddIdForContainedElements(existingElements, addedElement.Value.Line, addedElement); + } + } + } + + private static void SetAddIdForContainedElements(IEnumerable existingElements, Line overrideLine, InteriorPartitionTypesOverrideAddition addOverride) + { + var containedElements = existingElements.Where(e => Line.PointOnLine(e.Line.Start, overrideLine.Start, overrideLine.End, true, 0.01) + && Line.PointOnLine(e.Line.End, overrideLine.Start, overrideLine.End, true, 0.01)); + foreach (var containedElement in containedElements) + { + containedElement.AddId = addOverride.Id; + } + } + + private static void RemoveWallCandidates(IList removals, List wallCandidates) + { + if (removals != null) + { + foreach (var removedElement in removals) + { + var elementToRemove = wallCandidates.FirstOrDefault(e => MatchIdentityWallCandidate(e, removedElement.Identity)); + if (elementToRemove != null) + { + wallCandidates.Remove(elementToRemove); + } + } + } } } } \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTest.cs b/LayoutFunctions/InteriorPartitions/test/OverridesTest.cs new file mode 100644 index 00000000..4662865c --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTest.cs @@ -0,0 +1,105 @@ +using Elements; +using Xunit; +using System; +using System.IO; +using System.Collections.Generic; +using Elements.Serialization.glTF; +using Elements.Geometry; +using System.Linq; + +namespace InteriorPartitions.Tests +{ + public class OverridesTest + { + private InteriorPartitionsInputs GetInput(string filename) + { + var json = File.ReadAllText($"../../../OverridesTestData/{filename}.json"); + return Newtonsoft.Json.JsonConvert.DeserializeObject(json); + } + + private static List CreateInteriorPartitions() + { + var interiorPartitionCandidates = new List(); + var wallCandidateLines = new List<(Line line, string type)>(); + wallCandidateLines.Add((new Line(new Vector3(), new Vector3(30, 0)), "Solid")); + interiorPartitionCandidates.Add(new InteriorPartitionCandidate(Guid.NewGuid()) + { + WallCandidateLines = wallCandidateLines, + Height = 3, + LevelTransform = new Transform() + }); + return interiorPartitionCandidates; + } + + [Fact] + public void SplitInTheMiddle() + { + var input = GetInput("inputs1"); + List interiorPartitionCandidates = CreateInteriorPartitions(); + var wallCandidates = InteriorPartitions.CreateWallCandidates(input, interiorPartitionCandidates); + Assert.Equal(3, wallCandidates.Count); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(), new Vector3(10, 0)), false)); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(10, 0), new Vector3(20, 0)), false)); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(20, 0), new Vector3(30, 0)), false)); + } + + [Fact] + public void RevertEditedPartAfterSplitInTheMiddle() + { + var input = GetInput("inputs2"); + List interiorPartitionCandidates = CreateInteriorPartitions(); + var wallCandidates = InteriorPartitions.CreateWallCandidates(input, interiorPartitionCandidates); + Assert.Equal(3, wallCandidates.Count); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(), new Vector3(10, 0)), false)); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(10, 0), new Vector3(20, 0)), false)); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(20, 0), new Vector3(30, 0)), false)); + } + + [Fact] + public void UpdateRevertedPartAfterSplit() + { + var input = GetInput("inputs3"); + List interiorPartitionCandidates = CreateInteriorPartitions(); + var wallCandidates = InteriorPartitions.CreateWallCandidates(input, interiorPartitionCandidates); + Assert.Equal(3, wallCandidates.Count); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(0, 10), new Vector3(10, 0)), false) && w.Type.Equals("Solid")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(10, 0), new Vector3(20, 0)), false) && w.Type.Equals("Glass")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(20, 0), new Vector3(30, 0)), false) && w.Type.Equals("Glass")); + } + + [Fact] + public void AddOverlappingWall() + { + var input = GetInput("inputs4"); + List interiorPartitionCandidates = CreateInteriorPartitions(); + var wallCandidates = InteriorPartitions.CreateWallCandidates(input, interiorPartitionCandidates); + Assert.Equal(3, wallCandidates.Count); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(-10, 0), new Vector3(0, 0)), false) && w.Type.Equals("Solid")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(0, 0), new Vector3(10, 0)), false) && w.Type.Equals("Glass")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(10, 0), new Vector3(30, 0)), false) && w.Type.Equals("Glass")); + } + + [Fact] + public void SplitInTheMiddleAndRemoveMiddlePart() + { + var input = GetInput("inputs5"); + List interiorPartitionCandidates = CreateInteriorPartitions(); + var wallCandidates = InteriorPartitions.CreateWallCandidates(input, interiorPartitionCandidates); + Assert.Equal(2, wallCandidates.Count); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(0, 0), new Vector3(10, 0)), false) && w.Type.Equals("Glass")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(20, 0), new Vector3(30, 0)), false) && w.Type.Equals("Glass")); + } + + [Fact] + public void SplitInTheMiddleRemoveAndAddAgainMiddlePart() + { + var input = GetInput("inputs6"); + List interiorPartitionCandidates = CreateInteriorPartitions(); + var wallCandidates = InteriorPartitions.CreateWallCandidates(input, interiorPartitionCandidates); + Assert.Equal(3, wallCandidates.Count); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(0, 0), new Vector3(10, 0)), false) && w.Type.Equals("Glass")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(10, 0), new Vector3(20, 0)), false) && w.Type.Equals("Solid")); + Assert.Contains(wallCandidates, w => w.Line.IsAlmostEqualTo(new Line(new Vector3(20, 0), new Vector3(30, 0)), false) && w.Type.Equals("Glass")); + } + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs1.json b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs1.json new file mode 100644 index 00000000..e8426baa --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs1.json @@ -0,0 +1,86 @@ +{ + "overrides": { + "additions": { + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "3b45b9f4-984b-4939-a929-ef3f9a529c58" + }, + "id": "cf9dfc48-9484-4647-a026-6a51c827bd56" + }, + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "9b206979-46b4-4094-b62a-a0088a162e56" + }, + "id": "f0ed8ce2-bd15-4317-900d-736a3063ead3" + } + ] + }, + "removals": {}, + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + } + }, + "id": "42f54022-b609-4ad1-9ddd-2e76e2b4053a" + } + ] + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs2.json b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs2.json new file mode 100644 index 00000000..529eef52 --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs2.json @@ -0,0 +1,50 @@ +{ + "overrides": { + "additions": { + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "3b45b9f4-984b-4939-a929-ef3f9a529c58" + }, + "id": "cf9dfc48-9484-4647-a026-6a51c827bd56" + }, + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "9b206979-46b4-4094-b62a-a0088a162e56" + }, + "id": "f0ed8ce2-bd15-4317-900d-736a3063ead3" + } + ] + }, + "removals": {}, + "Interior Partition Types": [] + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs3.json b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs3.json new file mode 100644 index 00000000..f3259e94 --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs3.json @@ -0,0 +1,158 @@ +{ + "overrides": { + "additions": { + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "3b45b9f4-984b-4939-a929-ef3f9a529c58" + }, + "id": "cf9dfc48-9484-4647-a026-6a51c827bd56" + }, + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "9b206979-46b4-4094-b62a-a0088a162e56" + }, + "id": "f0ed8ce2-bd15-4317-900d-736a3063ead3" + } + ] + }, + "removals": {}, + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 0, + "Y": 10.000000000000002, + "Z": 0 + }, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Solid" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + } + }, + "id": "a1010a87-d615-4fa1-840f-6a8b14c3fa32" + }, + { + "value": { + "Line": { + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "AddId": "cf9dfc48-9484-4647-a026-6a51c827bd56" + }, + "id": "de0e49ed-70a7-416d-b4ab-ba2e4d8209a3" + }, + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "AddId": "f0ed8ce2-bd15-4317-900d-736a3063ead3" + }, + "id": "8bc23230-fb3f-4e99-b9f6-44ea1d2a2fb4" + } + ] + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs4.json b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs4.json new file mode 100644 index 00000000..b9fa45da --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs4.json @@ -0,0 +1,86 @@ +{ + "overrides": { + "additions": { + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "dd48b824-2575-4daf-92e7-232bc951307c" + }, + "id": "06f46edc-dec9-42af-bf58-92b9df0bc427" + }, + { + "value": { + "Line": { + "Start": { + "X": -10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Solid", + "Id": "15961c39-55dd-4ec0-a983-e52b69a9ed28" + }, + "id": "df7e2169-0147-4997-9607-f35ed355d05b" + } + ] + }, + "removals": {}, + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + } + }, + "id": "b1a7e987-f753-47d5-bdf3-c633ba58b63b" + } + ] + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs5.json b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs5.json new file mode 100644 index 00000000..5d1a5e2e --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs5.json @@ -0,0 +1,68 @@ +{ + "overrides": { + "additions": { + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "6d2439dc-d83f-4ccf-922a-5492ac20fb26" + }, + "id": "b04484f9-a9cc-4ff2-a29e-b6be9d316344" + } + ] + }, + "removals": { + "Interior Partition Types": [] + }, + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + } + }, + "id": "ab249b29-d04c-4e50-8528-6502e971374c" + } + ] + } +} \ No newline at end of file diff --git a/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs6.json b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs6.json new file mode 100644 index 00000000..083ddb10 --- /dev/null +++ b/LayoutFunctions/InteriorPartitions/test/OverridesTestData/inputs6.json @@ -0,0 +1,124 @@ +{ + "overrides": { + "additions": { + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass", + "Id": "6d2439dc-d83f-4ccf-922a-5492ac20fb26" + }, + "id": "b04484f9-a9cc-4ff2-a29e-b6be9d316344" + }, + { + "value": { + "Line": { + "Start": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Solid", + "Id": "2ba6831b-60f2-4595-8c41-c4630fca2abb" + }, + "id": "b3949a6b-5997-47f3-b176-3eb3897e2841" + } + ] + }, + "removals": { + "Interior Partition Types": [] + }, + "Interior Partition Types": [ + { + "value": { + "Line": { + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 10, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + } + }, + "id": "f59ce751-420b-49d6-91c4-dbd29d76e7e5" + }, + { + "value": { + "Line": { + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "Type": "Glass" + }, + "identity": { + "Line": { + "IsClosedForRendering": false, + "End": { + "X": 30, + "Y": 0, + "Z": 0 + }, + "Start": { + "X": 20, + "Y": 0, + "Z": 0 + }, + "discriminator": "Elements.Geometry.Line" + }, + "AddId": "b04484f9-a9cc-4ff2-a29e-b6be9d316344" + }, + "id": "6de3e451-592d-4b8f-9c58-be4dcf1b35d1" + } + ] + } +} \ No newline at end of file diff --git a/LayoutFunctions/LayoutFunctionCommon/WallGeneration.cs b/LayoutFunctions/LayoutFunctionCommon/WallGeneration.cs index 0e3beef6..cfa7e60d 100644 --- a/LayoutFunctions/LayoutFunctionCommon/WallGeneration.cs +++ b/LayoutFunctions/LayoutFunctionCommon/WallGeneration.cs @@ -128,13 +128,13 @@ public static class WallGeneration } else if (totalCount == 0) // end segment { - AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart, point); + AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart.pos, point.pos, point.type); } else if (segmentStart.type.Equals(point.type)) { if (typePointsCounts[segmentStart.type] == 0) // end segment with current type { - AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart, point); + AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart.pos, point.pos, point.type); var nextType = typePointsCounts .FirstOrDefault(t => interiorPartitionTypePriority[t.Key] < interiorPartitionTypePriority[point.type] && t.Value > 0); @@ -149,7 +149,7 @@ public static class WallGeneration // new type with higher priority starts if (interiorPartitionTypePriority[point.type] > interiorPartitionTypePriority[segmentStart.type]) { - AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart, (point.pos, point.isEnd, segmentStart.type)); + AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart.pos, point.pos, segmentStart.type); segmentStart = point; } } @@ -159,7 +159,7 @@ public static class WallGeneration return resultCandidates; } - private static Dictionary> GroupCollinearLines(IEnumerable<(Line line, string type)> typedLines) + private static Dictionary> GroupCollinearLines(IEnumerable<(Line line, string type)> typedLines, double tolerance = Vector3.EPSILON) { var collinearLinesGroups = new Dictionary>(); foreach (var typedLine in typedLines) @@ -167,7 +167,7 @@ public static class WallGeneration var isLineAdded = false; foreach (var linesGroup in collinearLinesGroups) { - if (typedLine.line.IsCollinear(linesGroup.Key)) + if (typedLine.line.IsCollinear(linesGroup.Key, tolerance)) { linesGroup.Value.Add(typedLine); isLineAdded = true; @@ -183,17 +183,115 @@ public static class WallGeneration return collinearLinesGroups; } - private static void AddWallCandidateLine(List<(Line line, string type)> resultCandidates, Line dominantLineForGroup, Vector3 domLineDir, (double pos, bool isEnd, string type) segmentStart, (double pos, bool isEnd, string type) point) + private static void AddWallCandidateLine(List<(Line line, string type)> resultCandidates, Line dominantLineForGroup, Vector3 domLineDir, double startPos, double endPos, string type) { - var startPt = segmentStart.pos * domLineDir + dominantLineForGroup.Start; - var endPt = point.pos * domLineDir + dominantLineForGroup.Start; + var startPt = startPos * domLineDir + dominantLineForGroup.Start; + var endPt = endPos * domLineDir + dominantLineForGroup.Start; if (startPt.DistanceTo(endPt) > 0.01) { var newLine = new Line(startPt, endPt); - resultCandidates.Add((newLine, point.type)); + resultCandidates.Add((newLine, type)); } } + public static List<(Line line, string type)> SplitOverlappingWallCandidates(IEnumerable<(Line line, string type)> wallCandidateLines, IEnumerable<(Line line, string type)> prioritizedWallCandidateLines, double tolerance = 0.01) + { + var resultCandidates = new List<(Line line, string type)>(); + var typedLines = wallCandidateLines.Where(l => interiorPartitionTypePriority.ContainsKey(l.type)); + var prioritizedTypedLines = prioritizedWallCandidateLines.Where(l => interiorPartitionTypePriority.ContainsKey(l.type)); + var allTypedLines = new List<(Line, string)>(); + allTypedLines.AddRange(typedLines.Select(l => (l.line, $"{l.type}-0"))); + allTypedLines.AddRange(prioritizedTypedLines.Select(l => (l.line, $"{l.type}-1"))); + var collinearLinesGroups = GroupCollinearLines(allTypedLines, tolerance); + + foreach (var collinearLinesGroup in collinearLinesGroups) + { + if (collinearLinesGroup.Value.Count == 1) + { + var candidate = collinearLinesGroup.Value.First(); + candidate.type = candidate.type.Split('-').First(); + resultCandidates.Add(candidate); + continue; + } + var linesOrderedByLength = collinearLinesGroup.Value.OrderByDescending(v => v.line.Length()); + var dominantLineForGroup = linesOrderedByLength.First().line; + var domLineDir = dominantLineForGroup.Direction(); + + var orderEnds = new List<(double pos, bool isEnd, string type, int priority)>(); + foreach (var linePair in collinearLinesGroup.Value) + { + var line = linePair.line; + var start = (line.Start - dominantLineForGroup.Start).Dot(domLineDir); + var end = (line.End - dominantLineForGroup.Start).Dot(domLineDir); + if (start > end) + { + var oldStart = start; + start = end; + end = oldStart; + } + + var typePriorityStrings = linePair.type.Split('-'); + orderEnds.Add((start, false, typePriorityStrings[0], int.Parse(typePriorityStrings[1]))); + orderEnds.Add((end, true, typePriorityStrings[0], int.Parse(typePriorityStrings[1]))); + } + + var totalCount = 0; + (double pos, bool isEnd, string type, int priority) segmentStart = default; + var endsOrdered = orderEnds.OrderBy(e => e.pos).ThenBy(e => !e.isEnd); + var typePointsCounts = new Dictionary<(string type, int priority), int>(); + foreach (var point in endsOrdered) + { + var prevCount = totalCount; + typePointsCounts.TryGetValue((point.type, point.priority), out var typeCount); + var delta = point.isEnd ? -1 : 1; + totalCount += delta; + typeCount += delta; + typePointsCounts[(point.type, point.priority)] = typeCount; + if (totalCount == 1 && prevCount == 0) // begin segment + { + segmentStart = point; + } + else if (totalCount == 0) // end segment + { + AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart.pos, point.pos, point.type); + } + else + { + AddWallCandidateLine(resultCandidates, dominantLineForGroup, domLineDir, segmentStart.pos, point.pos, segmentStart.type); + + if (segmentStart.priority < point.priority) + { + segmentStart = point; + } + else if (segmentStart.priority > point.priority) + { + segmentStart = (point.pos, false, segmentStart.type, segmentStart.priority); + } + else + { + var nextType = typePointsCounts + .OrderByDescending(t => t.Key.priority) + .ThenByDescending(t => interiorPartitionTypePriority[t.Key.type]) + .FirstOrDefault(t => (t.Key.priority == point.priority + && interiorPartitionTypePriority[t.Key.type] < interiorPartitionTypePriority[point.type] + || t.Key.priority < point.priority) + && t.Value > 0); + if (nextType.Key.type != null) // start segment with lower priority if it exists + { + segmentStart = (point.pos, false, nextType.Key.type, nextType.Key.priority); + } + else + { + segmentStart = (point.pos, false, segmentStart.type, segmentStart.priority); + } + } + } + } + } + + return resultCandidates; + } + private static double CalculateTotalStorefrontHeight(double volumeHeight) { return Math.Min(2.7, volumeHeight);