diff --git a/Components/ModuleConstruct.cs b/Components/ModuleConstruct.cs index a293018..a463a9c 100644 --- a/Components/ModuleConstruct.cs +++ b/Components/ModuleConstruct.cs @@ -175,7 +175,7 @@ protected override void SolveInstance(IGH_DataAccess DA) { return; } - if (name.Contains("\n") || name.Contains(":") || name.Contains("=")) { + if (Config.RESERVED_CHARS.Any(chars => name.Contains(chars))) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Monoceros Module name contains " + "a forbidden content: :, ->, = or newline."); return; diff --git a/Components/RuleOuterContruct.cs b/Components/RuleAtBoundaryContruct.cs similarity index 99% rename from Components/RuleOuterContruct.cs rename to Components/RuleAtBoundaryContruct.cs index 83908f4..f7e02d2 100644 --- a/Components/RuleOuterContruct.cs +++ b/Components/RuleAtBoundaryContruct.cs @@ -132,7 +132,7 @@ private uint DirectionToSingleModuleConnectorIndex(Direction direction) { /// Provides an Icon for every component that will be visible in the /// User Interface. Icons need to be 24x24 pixels. /// - protected override System.Drawing.Bitmap Icon => Properties.Resources.rule_out; + protected override System.Drawing.Bitmap Icon => Properties.Resources.rule_out_construct; /// /// Each component must have a unique Guid to identify it. It is vital diff --git a/Components/RuleOuterFromPoint.cs b/Components/RuleAtBoundaryFromPoint.cs similarity index 100% rename from Components/RuleOuterFromPoint.cs rename to Components/RuleAtBoundaryFromPoint.cs diff --git a/Components/RuleExplicitConstruct.cs b/Components/RuleExplicitConstruct.cs index e971039..7b71a35 100644 --- a/Components/RuleExplicitConstruct.cs +++ b/Components/RuleExplicitConstruct.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Grasshopper.Kernel; namespace Monoceros { @@ -83,12 +84,8 @@ protected override void SolveInstance(IGH_DataAccess DA) { var sourceName = sourceNameRaw.Name; var targetName = targetNameRaw.Name; - if (sourceName.Contains("\n") - || sourceName.Contains(":") - || sourceName.Contains("=") - || targetName.Contains("\n") - || targetName.Contains(":") - || targetName.Contains("=")) { + if (Config.RESERVED_CHARS.Any(chars => sourceName.Contains(chars)) + || Config.RESERVED_CHARS.Any(chars => targetName.Contains(chars))) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Input text contains " + "a forbidden content: :, ->, = or newline."); return; diff --git a/Components/RuleFromSlots.cs b/Components/RuleFromSlots.cs new file mode 100644 index 0000000..bd7fde9 --- /dev/null +++ b/Components/RuleFromSlots.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using Grasshopper; +using Grasshopper.Kernel; +using Grasshopper.Kernel.Data; +using Rhino; +using Rhino.DocObjects; +using Rhino.Geometry; + +namespace Monoceros { + public class ComponentScanSlotsForRules : GH_Component { + + public ComponentScanSlotsForRules( ) : base("Scan Slots For rules", + "RulesScan", + "Scan solved Slots and extract applied Rules.", + "Monoceros", + "Rule") { + } + + /// + /// Registers all the input parameters for this component. + /// + protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { + pManager.AddParameter(new SlotParameter(), + "Slots", + "S", + "Monoceros Slots", + GH_ParamAccess.list); + pManager.AddParameter(new ModuleParameter(), + "Modules", + "M", + "All Monoceros Modules", + GH_ParamAccess.list); + } + + /// + /// Registers all the output parameters for this component. + /// + protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { + pManager.AddParameter(new RuleParameter(), + "Rules Explicit", + "R", + "Explicit Monoceros Rules", + GH_ParamAccess.list); + } + + /// + /// Wrap input geometry into module cages. + /// + /// The DA object can be used to retrieve data from + /// input parameters and to store data in output parameters. + protected override void SolveInstance(IGH_DataAccess DA) { + var modulesRaw = new List(); + var slotsRaw = new List(); + + if (!DA.GetDataList(0, slotsRaw)) { + return; + } + + if (!DA.GetDataList(1, modulesRaw)) { + return; + } + + var slotsClean = new List(); + foreach (var slot in slotsRaw) { + if (slot == null || !slot.IsValid) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Slot is null or invalid and will be skipped."); + continue; + } + if (!slot.IsDeterministic) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Slot is not deterministic and will be skipped."); + continue; + } + slotsClean.Add(slot); + } + + var modulesClean = new List(); + foreach (var module in modulesRaw) { + if (module == null || !module.IsValid) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Module is null or invalid."); + continue; + } + if (module.Geometry.Count + module.ReferencedGeometry.Count == 0) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Module \"" + module.Name + "\" contains " + + "no geometry and therefore will be skipped."); + continue; + } + + modulesClean.Add(module); + } + + if (!modulesClean.Any()) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "No valid Modules collected."); + return; + } + + var slotsNonEmpty = slotsClean.Where(slot => + modulesClean.Any(module => module.ContainsPart(slot.AllowedPartNames[0])) + ); + + if (!slotsNonEmpty.Any()) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "No valid Slots collected."); + return; + } + + var rulesOut = new List(); + foreach (var slot in slotsNonEmpty) { + foreach (var slotOther in slotsNonEmpty) { + if (slot.RelativeCenter.IsNeighbor(slotOther.RelativeCenter)) { + var neighborVector = (slotOther.RelativeCenter - slot.RelativeCenter).ToVector3d(); + if (Direction.FromVector(neighborVector, out var direction) + && direction.Orientation == Orientation.Positive) { + var currentPart = slot.AllowedPartNames[0]; + var otherPart = slotOther.AllowedPartNames[0]; + var ruleForSolver = new RuleForSolver(direction.Axis, currentPart, otherPart); + if (RuleExplicit.FromRuleForSolver(ruleForSolver, modulesClean, out var ruleExplicit)) { + var rule = new Rule(ruleExplicit); + if (!rulesOut.Contains(rule)) { + rulesOut.Add(rule); + } + } + } + } + } + } + + rulesOut.Sort(); + DA.SetDataList(0, rulesOut); + } + + /// + /// The Exposure property controls where in the panel a component icon + /// will appear. There are seven possible locations (primary to + /// septenary), each of which can be combined with the + /// GH_Exposure.obscure flag, which ensures the component will only be + /// visible on panel dropdowns. + /// + public override GH_Exposure Exposure => GH_Exposure.quinary; + + /// + /// Provides an Icon for every component that will be visible in the + /// User Interface. Icons need to be 24x24 pixels. + /// + protected override Bitmap Icon => Properties.Resources.rules_from_slots; + + /// + /// Each component must have a unique Guid to identify it. It is vital + /// this Guid doesn't change otherwise old ghx files that use the old ID + /// will partially fail during loading. + /// + public override Guid ComponentGuid => new Guid("14CD0308-26FC-4134-AB5A-C7B89B6405BF"); + } +} diff --git a/Components/RuleIndifferentConstruct.cs b/Components/RuleIndifferentConstruct.cs index 6bbb96c..0065429 100644 --- a/Components/RuleIndifferentConstruct.cs +++ b/Components/RuleIndifferentConstruct.cs @@ -78,7 +78,7 @@ protected override void SolveInstance(IGH_DataAccess DA) { /// Provides an Icon for every component that will be visible in the /// User Interface. Icons need to be 24x24 pixels. /// - protected override System.Drawing.Bitmap Icon => Properties.Resources.rule_indifferent; + protected override System.Drawing.Bitmap Icon => Properties.Resources.rule_indifferent_construct; /// /// Each component must have a unique Guid to identify it. It is vital diff --git a/Components/RuleTypedConstruct.cs b/Components/RuleTypedConstruct.cs index 61d7c5e..c15965a 100644 --- a/Components/RuleTypedConstruct.cs +++ b/Components/RuleTypedConstruct.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Grasshopper.Kernel; namespace Monoceros { @@ -68,12 +69,8 @@ protected override void SolveInstance(IGH_DataAccess DA) { var moduleName = moduleNameRaw.Name; - if (moduleName.Contains("\n") - || moduleName.Contains(":") - || moduleName.Contains("=") - || type.Contains("\n") - || type.Contains(":") - || type.Contains("=")) { + if (Config.RESERVED_CHARS.Any(chars => moduleName.Contains(chars)) + || Config.RESERVED_CHARS.Any(chars => type.Contains(chars))) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Input text contains " + "a forbidden content: :, ->, = or newline."); return; diff --git a/Components/RuleTypedFromPoint.cs b/Components/RuleTypedFromPoint.cs index f6bfaef..8e51787 100644 --- a/Components/RuleTypedFromPoint.cs +++ b/Components/RuleTypedFromPoint.cs @@ -68,9 +68,7 @@ protected override void SolveInstance(IGH_DataAccess DA) { return; } - if (type.Contains("\n") - || type.Contains(":") - || type.Contains("=")) { + if (Config.RESERVED_CHARS.Any(chars => type.Contains(chars))) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Input text contains " + "a forbidden content: :, ->, = or newline."); return; diff --git a/Components/PopulateGeometryWithSlotCenters.cs b/Components/SliceGeometry.cs similarity index 100% rename from Components/PopulateGeometryWithSlotCenters.cs rename to Components/SliceGeometry.cs diff --git a/Components/SlotConstructWithModules.cs b/Components/SlotConstructWithModules.cs index 91ccfc6..90260a4 100644 --- a/Components/SlotConstructWithModules.cs +++ b/Components/SlotConstructWithModules.cs @@ -91,11 +91,9 @@ protected override void SolveInstance(IGH_DataAccess DA) { var allowedModules = allowedModulesRaw.Select(name => name.Name).Distinct().ToList(); - if (allowedModules.Any(name => name.Contains("\n") - || name.Contains(":") - || name.Contains("="))) { + if (Config.RESERVED_CHARS.Any(chars => allowedModules.Contains(chars))) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Input text contains " + - "a forbidden content: :, ->, = or newline."); + "a forbidden content: :, ->, = or newline."); return; } diff --git a/Components/SlotDeconstruct.cs b/Components/SlotDeconstruct.cs index 4b6bab0..36f2ef3 100644 --- a/Components/SlotDeconstruct.cs +++ b/Components/SlotDeconstruct.cs @@ -115,7 +115,7 @@ protected override void SolveInstance(IGH_DataAccess DA) { } else { DA.SetDataList(4, new List() { slot.AllowsAnyModule }); } - DA.SetDataList(5, new List() { slot.AllowsNothing }); + DA.SetDataList(5, new List() { slot.IsContradictory }); if (modulesProvided && moduleNames != null && (slot.AllowedModuleNames.Count > moduleNames.Count diff --git a/Components/SlotsAddBoundary.cs b/Components/SlotsAddBoundary.cs index 5e2c5c2..6cedb0a 100644 --- a/Components/SlotsAddBoundary.cs +++ b/Components/SlotsAddBoundary.cs @@ -150,7 +150,7 @@ protected override void SolveInstance(IGH_DataAccess DA) { /// Provides an Icon for every component that will be visible in the /// User Interface. Icons need to be 24x24 pixels. /// - protected override Bitmap Icon => Properties.Resources.slot_add_boundary_2; + protected override Bitmap Icon => Properties.Resources.slot_add_boundary; /// /// Each component must have a unique Guid to identify it. It is vital diff --git a/Components/SlotsFromGeometry.cs b/Components/SlotsFromGeometry.cs new file mode 100644 index 0000000..366a728 --- /dev/null +++ b/Components/SlotsFromGeometry.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Grasshopper.Kernel; +using Grasshopper.Kernel.Types; +using Rhino; +using Rhino.Geometry; + +namespace Monoceros { + public class ComponentSlotsFromgeometry : GH_Component { + public ComponentSlotsFromgeometry( ) : base("Slots FromGeometry", + "SlotsFromGeometry", + "Identify Module geometry and construct Slots containing it. Ignores connection to boundary.", + "Monoceros", "Slot") { + } + + /// + /// Registers all the input parameters for this component. + /// + protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { + pManager.AddGeometryParameter("Geometry", + "G", + "Geometry to scan and identify Modules", + GH_ParamAccess.list); + pManager.AddParameter(new ModuleParameter(), + "Module", + "M", + "Monoceros Module", + GH_ParamAccess.item); + pManager.AddPlaneParameter("Base Plane", + "B", + "Grid space base plane of the scanned geometry. Defines orientation of the grid.", + GH_ParamAccess.item, + Plane.WorldXY); + } + + /// + /// Registers all the output parameters for this component. + /// + protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { + pManager.AddParameter(new SlotParameter(), "Slots", "S", "Monoceros Slots", GH_ParamAccess.list); + } + + /// + /// Wrap input geometry into module cages. + /// + /// The DA object can be used to retrieve data from + /// input parameters and to store data in output parameters. + protected override void SolveInstance(IGH_DataAccess DA) { + var geometryRaw = new List(); + var module = new Module(); + var basePlane = new Plane(); + + if (!DA.GetDataList(0, geometryRaw)) { + return; + } + + if (!DA.GetData(1, ref module)) { + return; + } + + if (!DA.GetData(2, ref basePlane)) { + return; + } + + if (module == null || !module.IsValid) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Module is null or invalid."); + return; + } + + var geometryTransform = Transform.PlaneToPlane(basePlane, Plane.WorldXY); + var geometryClean = geometryRaw + .Select(goo => GH_Convert.ToGeometryBase(goo)) + .Where(geo => geo != null) + .Select(geo => { + var transformedGeometry = geo.Duplicate(); + transformedGeometry.Transform(geometryTransform); + return transformedGeometry; + }).ToList(); + if (!geometryClean.Any()) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, + "Failed to collect any valid geometry to scan."); + return; + } + + var moduleTransform = Transform.PlaneToPlane(module.BasePlane, Plane.WorldXY); + var moduleGeometry = module.Geometry + .Concat(module.ReferencedGeometry) + .Select(geo => { + var transformedGeometry = geo.Duplicate(); + transformedGeometry.Transform(moduleTransform); + return transformedGeometry; + }); + var modulePartCenters = module.PartCenters + .Select(point => { + var transformedPoint = point.ToCartesian(module.BasePlane, module.PartDiagonal); + transformedPoint.Transform(moduleTransform); + return transformedPoint; + }); + if (!moduleGeometry.Any()) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, + "Module \"" + module.Name + "\" contains " + + "no geometry and therefore will be skipped."); + return; + } + + var moduleBBoxes = moduleGeometry.Select(geo => geo.GetBoundingBox(false)); + var modulePivot = module.Pivot; + modulePivot.Transform(moduleTransform); + var modulePivotOrigin = modulePivot.Origin; + + var commonModuleBBox = BoundingBox.Empty; + commonModuleBBox.Union(modulePivotOrigin); + + foreach (var moduleBBox in moduleBBoxes) { + commonModuleBBox.Union(moduleBBox); + } + + var safetyBuffer = new Point3i( + (int)Math.Ceiling(commonModuleBBox.Diagonal.X / module.PartDiagonal.X) + 1, + (int)Math.Ceiling(commonModuleBBox.Diagonal.Y / module.PartDiagonal.Y) + 1, + (int)Math.Ceiling(commonModuleBBox.Diagonal.Z / module.PartDiagonal.Z) + 1); + + var geometryBBoxes = geometryClean.Select(geo => geo.GetBoundingBox(false)).ToList(); + var commonBBox = BoundingBox.Empty; + + foreach (var bBox in geometryBBoxes) { + commonBBox.Union(bBox); + } + + var slots = new List(); + + for (int z = (int)Math.Floor(commonBBox.Min.Z / module.PartDiagonal.Z) - safetyBuffer.Z; + z < Math.Ceiling(commonBBox.Max.Z / module.PartDiagonal.Z) + safetyBuffer.Z; + z++) { + for (int y = (int)Math.Floor(commonBBox.Min.Y / module.PartDiagonal.Y) - safetyBuffer.Y; + y < Math.Ceiling(commonBBox.Max.Y / module.PartDiagonal.Y) + safetyBuffer.Y; + y++) { + for (int x = (int)Math.Floor(commonBBox.Min.X / module.PartDiagonal.X) - safetyBuffer.X; + x < Math.Ceiling(commonBBox.Max.X / module.PartDiagonal.X) + safetyBuffer.X; + x++) { + var currentPivot = Plane.WorldXY; + var currentPivotOrigin = new Point3d(x * module.PartDiagonal.X, + y * module.PartDiagonal.Y, + z * module.PartDiagonal.Z); + currentPivot.Origin = currentPivotOrigin; + var currentTransform = Transform.PlaneToPlane(modulePivot, currentPivot); + var transformedModuleBBoxes = moduleBBoxes.Select(bBox => { + var transformedBBox = bBox; + transformedBBox.Transform(currentTransform); + return transformedBBox; + }); + + var indicesOfSimilarGeometries = new List(); + var allModuleBoxesFoundSimilarGeometryBox = true; + foreach (var currentBBox in transformedModuleBBoxes) { + var foundBoxForCurrent = false; + var otherIndex = 0; + foreach (var otherBBox in geometryBBoxes) { + var currentPoints = currentBBox.GetCorners(); + var otherPoints = otherBBox.GetCorners(); + for (var i = 0; i < currentPoints.Length; i++) { + var currentPoint = currentPoints[i]; + var otherPoint = otherPoints[i]; + var equalPoints = currentPoint.EpsilonEquals(otherPoint, RhinoMath.SqrtEpsilon); + if (!equalPoints) { + break; + } + foundBoxForCurrent = true; + } + if (foundBoxForCurrent) { + indicesOfSimilarGeometries.Add(otherIndex); + break; + } + otherIndex++; + } + if (!foundBoxForCurrent) { + allModuleBoxesFoundSimilarGeometryBox = false; + break; + } + } + + if (!allModuleBoxesFoundSimilarGeometryBox) { + continue; + } + + // Heavy calculations + + var transformedModuleGeometry = moduleGeometry.Select(geo => { + var transformedGeometry = geo.Duplicate(); + transformedGeometry.Transform(currentTransform); + return transformedGeometry; + }); + + var geometriesToCheck = indicesOfSimilarGeometries.Select(index => geometryClean[index]); + + var geometryEqualityPattern = transformedModuleGeometry + .Zip(geometriesToCheck, (current, other) => GeometryBase.GeometryEquals(current, other)); + + if (geometryEqualityPattern.All(equal => equal)) { + + var transformedModulePartCenters = modulePartCenters.Select(centerPoint => { + var transformedPoint = centerPoint; + transformedPoint.Transform(currentTransform); + return transformedPoint; + }); + + var currentPivotCenter = new Point3d(currentPivotOrigin.X / module.PartDiagonal.X, + currentPivotOrigin.Y / module.PartDiagonal.Y, + currentPivotOrigin.Z / module.PartDiagonal.Z); + var currentPivotRelativeCenter = new Point3i(currentPivotCenter); + var firstModulePartRelativeCenter = module.PartCenters[0]; + var modulePartsRelativeCentersRelativeToModulePivot = module.PartCenters.Select(center => center - firstModulePartRelativeCenter); + + var currentModuleSlots = modulePartsRelativeCentersRelativeToModulePivot + .Zip( + module.PartNames, + (partCenter, partName) => new Slot(basePlane, + currentPivotRelativeCenter + partCenter, + module.PartDiagonal, + false, + new List() { module.Name }, + new List() { partName }, + 0)); + slots.AddRange(currentModuleSlots); + } + } + } + } + + if (!Slot.AreSlotLocationsUnique(slots)) { + AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Slot centers are not unique."); + } + + DA.SetDataList(0, slots); + } + + + /// + /// The Exposure property controls where in the panel a component icon + /// will appear. There are seven possible locations (primary to + /// septenary), each of which can be combined with the + /// GH_Exposure.obscure flag, which ensures the component will only be + /// visible on panel dropdowns. + /// + public override GH_Exposure Exposure => GH_Exposure.tertiary; + + /// + /// Provides an Icon for every component that will be visible in the + /// User Interface. Icons need to be 24x24 pixels. + /// + protected override System.Drawing.Bitmap Icon => Properties.Resources.slots_from_geometry; + + /// + /// Each component must have a unique Guid to identify it. It is vital + /// this Guid doesn't change otherwise old ghx files that use the old ID + /// will partially fail during loading. + /// + public override Guid ComponentGuid => new Guid("725A08EC-60D6-4F72-9EF4-BA82864085D4"); + } +} diff --git a/Components/Solver.cs b/Components/Solver.cs index dec7dcf..0efdcf8 100644 --- a/Components/Solver.cs +++ b/Components/Solver.cs @@ -424,7 +424,7 @@ protected override void SolveInstance(IGH_DataAccess DA) { return; } - if (slot.AllowsNothing) { + if (slot.IsContradictory) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Slot at " + slot.AbsoluteCenter + "does not allow any Module " + "to be placed."); diff --git a/Config.cs b/Config.cs index a679a69..8ec9a02 100644 --- a/Config.cs +++ b/Config.cs @@ -1,4 +1,5 @@ -using System.Drawing; +using System.Collections.Generic; +using System.Drawing; using System.Linq; namespace Monoceros { @@ -20,6 +21,10 @@ public class Config { /// public const string INDIFFERENT_TAG = "indifferent"; + public const string PART_SEPARATOR = "~"; + + public static List RESERVED_CHARS = new List() { "\n", ":", "=", PART_SEPARATOR }; + /// /// Collected reserved module names. /// diff --git a/Monoceros.csproj b/Monoceros.csproj index 5e6bfb0..b39d838 100644 --- a/Monoceros.csproj +++ b/Monoceros.csproj @@ -56,10 +56,12 @@ + + - + @@ -67,7 +69,7 @@ - + @@ -79,7 +81,7 @@ - + @@ -126,6 +128,11 @@ + + + + + @@ -154,7 +161,6 @@ - @@ -180,7 +186,7 @@ Erase "$(TargetPath)" en-US - C:\Program Files\Rhino 6\System\Rhino.exe + C:\Program Files\Rhino 7\System\Rhino.exe Program diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index a24417c..e4e67b1 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -35,5 +35,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.2.0")] -[assembly: AssemblyFileVersion("1.2.2.0")] +[assembly: AssemblyVersion("1.2.3.0")] +[assembly: AssemblyFileVersion("1.2.3.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 75ff884..7d391d6 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -230,6 +230,16 @@ internal static System.Drawing.Bitmap rule_indifferent { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap rule_indifferent_construct { + get { + object obj = ResourceManager.GetObject("rule_indifferent_construct", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -270,6 +280,16 @@ internal static System.Drawing.Bitmap rule_out { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap rule_out_construct { + get { + object obj = ResourceManager.GetObject("rule_out_construct", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -320,6 +340,16 @@ internal static System.Drawing.Bitmap rules_collect { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap rules_from_slots { + get { + object obj = ResourceManager.GetObject("rules_from_slots", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -333,9 +363,9 @@ internal static System.Drawing.Bitmap rules_unwrap { /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap slot_add_boundary_2 { + internal static System.Drawing.Bitmap slot_add_boundary { get { - object obj = ResourceManager.GetObject("slot_add_boundary_2", resourceCulture); + object obj = ResourceManager.GetObject("slot_add_boundary", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } @@ -400,6 +430,16 @@ internal static System.Drawing.Bitmap slot_transparent { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap slots_from_geometry { + get { + object obj = ResourceManager.GetObject("slots_from_geometry", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 16b87f2..7f27e06 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -151,6 +151,9 @@ ..\Resources\rules-collect.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\rules-from-slots.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\rules-unwrap.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -175,6 +178,9 @@ ..\Resources\rule-indifferent.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\rule-indifferent-construct.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\rule-indifferent-unused.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -187,6 +193,9 @@ ..\Resources\rule-out.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\rule-out-construct.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\rule-suggest.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -199,7 +208,10 @@ ..\Resources\rule-typed-transparent.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - + + ..\Resources\slots-from-geometry.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\slot-add-boundary-2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/Resources/rule-indifferent-construct.png b/Resources/rule-indifferent-construct.png new file mode 100644 index 0000000..f3c9713 Binary files /dev/null and b/Resources/rule-indifferent-construct.png differ diff --git a/Resources/rule-out-construct.png b/Resources/rule-out-construct.png new file mode 100644 index 0000000..1315680 Binary files /dev/null and b/Resources/rule-out-construct.png differ diff --git a/Resources/rule-out.png b/Resources/rule-out.png index cc8c5c7..893d1a6 100644 Binary files a/Resources/rule-out.png and b/Resources/rule-out.png differ diff --git a/Resources/rule-suggest.png b/Resources/rule-suggest.png index 3d7d5a6..d8c5146 100644 Binary files a/Resources/rule-suggest.png and b/Resources/rule-suggest.png differ diff --git a/Resources/rules-from-slots.png b/Resources/rules-from-slots.png new file mode 100644 index 0000000..a7d5e19 Binary files /dev/null and b/Resources/rules-from-slots.png differ diff --git a/Resources/slots-from-geometry.png b/Resources/slots-from-geometry.png new file mode 100644 index 0000000..1ee8283 Binary files /dev/null and b/Resources/slots-from-geometry.png differ diff --git a/Types/Direction.cs b/Types/Direction.cs index bd547fa..5e790ce 100644 --- a/Types/Direction.cs +++ b/Types/Direction.cs @@ -20,6 +20,37 @@ public Direction(Axis axis, Orientation orientation) { this.orientation = orientation; } + public static bool FromVector (Vector3d vector, out Direction direction) { + if (vector.Unitize()) { + if (vector.X == 1 && vector.Y == 0 && vector.Z == 0) { + direction = new Direction(Axis.X, Orientation.Positive); + return true; + } + if (vector.X == -1 && vector.Y == 0 && vector.Z == 0) { + direction = new Direction(Axis.X, Orientation.Negative); + return true; + } + if (vector.X == 0 && vector.Y == 1 && vector.Z == 0) { + direction = new Direction(Axis.Y, Orientation.Positive); + return true; + } + if (vector.X == 0 && vector.Y == -1 && vector.Z == 0) { + direction = new Direction(Axis.Y, Orientation.Negative); + return true; + } + if (vector.X == 0 && vector.Y == 0 && vector.Z == 1) { + direction = new Direction(Axis.Z, Orientation.Positive); + return true; + } + if (vector.X == 0 && vector.Y == 0 && vector.Z == -1) { + direction = new Direction(Axis.Z, Orientation.Negative); + return true; + } + } + direction = new Direction(); + return false; + } + /// /// Determines whether the other is opposite to /// the current. A is opposite when the diff --git a/Types/Module.cs b/Types/Module.cs index f996165..a8054ad 100644 --- a/Types/Module.cs +++ b/Types/Module.cs @@ -158,7 +158,7 @@ public Module(string name, Vector3d partDiagonal) { // Check if any part centers are defined if (!partCenters.Any()) { - throw new Exception("part centers list is empty"); + throw new Exception("Part centers list is empty"); } // Check if all the parts are unique @@ -183,7 +183,7 @@ public Module(string name, // Generate part names to be used as module names by the Monoceros solver PartNames = new List(); for (var i = 0; i < partCenters.Count; i++) { - PartNames.Add(name + i); + PartNames.Add(name + Config.PART_SEPARATOR + i); } // Place the pivot into the first part and orient is according to the base plane @@ -192,7 +192,7 @@ public Module(string name, pivot.Origin = PartCenters[0].ToCartesian(BasePlane, PartDiagonal); Pivot = pivot; // The name of the first part which should trigger the geometry placement - PivotPartName = Name + 0; + PivotPartName = Name + Config.PART_SEPARATOR + 0; // The connectors describe faces of parts and their relation to the entire module Connectors = ComputeModuleConnectors(PartCenters, @@ -470,6 +470,11 @@ private List ComputeInternalRules(List partCenters, return rulesInternal; } + /// + /// Checks if any Module Part name equals the test value. + /// + public bool ContainsPart(string partName) => PartNames.Any(name => name == partName); + /// /// Gets a value indicating whether the module is valid. Required by /// Grasshopper. diff --git a/Types/Rule.cs b/Types/Rule.cs index c6589ea..7b3d318 100644 --- a/Types/Rule.cs +++ b/Types/Rule.cs @@ -3,6 +3,8 @@ using System.Linq; using GH_IO.Serialization; using Grasshopper.Kernel.Types; +using Rhino; +using Rhino.Geometry; namespace Monoceros { /// @@ -555,6 +557,84 @@ public RuleExplicit(string sourceModuleName, TargetConnectorIndex = (int)targetConnectorIndex; } + public static bool FromRuleForSolver(RuleForSolver ruleForSolver, + IEnumerable modules, + out RuleExplicit ruleExplicit) { + ruleExplicit = new RuleExplicit("wrong", 0, "error", 0); + + var currentPartName = ruleForSolver.LowerPartName; + var otherPartName = ruleForSolver.HigherPartName; + var currentModule = modules.First(module => module.ContainsPart(currentPartName)); + var otherModule = modules.First(module => module.ContainsPart(otherPartName)); + + if (currentModule == null + || otherModule == null + // Rule is internal + || (currentModule == otherModule && currentModule.InternalRules.Contains(ruleForSolver))) { + return false; + } + var currentPartIndex = currentModule.PartNames.FindIndex(name => name == currentPartName); + var currentPartCenter = currentModule + .PartCenters[currentPartIndex] + .ToCartesian(currentModule.BasePlane, currentModule.PartDiagonal); + var currentConnectorVector = new Vector3d(); + if (ruleForSolver.Axis == Axis.X) { + currentConnectorVector = currentModule.BasePlane.XAxis; + currentConnectorVector.Unitize(); + currentConnectorVector *= currentModule.PartDiagonal.X / 2; + } + if (ruleForSolver.Axis == Axis.Y) { + currentConnectorVector = currentModule.BasePlane.YAxis; + currentConnectorVector.Unitize(); + currentConnectorVector *= currentModule.PartDiagonal.Y / 2; + } + if (ruleForSolver.Axis == Axis.Z) { + currentConnectorVector = currentModule.BasePlane.ZAxis; + currentConnectorVector.Unitize(); + currentConnectorVector *= currentModule.PartDiagonal.Z / 2; + } + var currentPartConnectorOrigin = currentPartCenter + currentConnectorVector; + var currentConnectorIndex = currentModule + .Connectors + .FindIndex(connector => connector.AnchorPlane.Origin.EpsilonEquals(currentPartConnectorOrigin, + RhinoMath.SqrtEpsilon)); + if (currentConnectorIndex == -1) { + return false; + } + + var otherPartIndex = otherModule.PartNames.FindIndex(name => name == otherPartName); + var otherPartCenter = otherModule + .PartCenters[otherPartIndex] + .ToCartesian(otherModule.BasePlane, otherModule.PartDiagonal); + var otherConnectorVector = new Vector3d(); + if (ruleForSolver.Axis == Axis.X) { + otherConnectorVector = otherModule.BasePlane.XAxis; + otherConnectorVector.Unitize(); + otherConnectorVector *= -otherModule.PartDiagonal.X / 2; + } + if (ruleForSolver.Axis == Axis.Y) { + otherConnectorVector = otherModule.BasePlane.YAxis; + otherConnectorVector.Unitize(); + otherConnectorVector *= -otherModule.PartDiagonal.Y / 2; + } + if (ruleForSolver.Axis == Axis.Z) { + otherConnectorVector = otherModule.BasePlane.ZAxis; + otherConnectorVector.Unitize(); + otherConnectorVector *= -otherModule.PartDiagonal.Z / 2; + } + var otherPartConnectorOrigin = otherPartCenter + otherConnectorVector; + var otherConnectorIndex = otherModule + .Connectors + .FindIndex(connector => connector.AnchorPlane.Origin.EpsilonEquals(otherPartConnectorOrigin, + RhinoMath.SqrtEpsilon)); + if (otherConnectorIndex == -1) { + return false; + } + + ruleExplicit = new RuleExplicit(currentModule.Name, (uint)currentConnectorIndex, otherModule.Name, (uint)otherConnectorIndex); + return true; + } + /// /// Checks if the provided object is equal to the current /// . The check is bi-directional, so an diff --git a/Types/Slot.cs b/Types/Slot.cs index 9c67c4f..f874abd 100644 --- a/Types/Slot.cs +++ b/Types/Slot.cs @@ -431,7 +431,7 @@ public void DrawViewportWires(GH_PreviewWireArgs args) { color = Config.CAGE_EVERYTHING_COLOR; } - if (AllowsNothing) { + if (IsContradictory) { color = Config.CAGE_NONE_COLOR; } @@ -463,7 +463,14 @@ public void DrawViewportWires(GH_PreviewWireArgs args) { /// not allow placement of any part and therefore also any /// . /// - public bool AllowsNothing => !(AllowsAnyModule || AllowedModuleNames.Any()); + public bool IsContradictory => !(AllowsAnyModule || AllowedModuleNames.Any()); + + /// + /// True if the is deterministic and therefore does + /// allow placement of exactly one Part. Such Slot + /// is considered to be solved and can be materialized. + /// + public bool IsDeterministic => AllowedPartNames.Count == 1; /// /// Checks the validity of a . Required by @@ -533,10 +540,10 @@ public override string ToString( ) { if (AllowsAnyModule) { containment = "all Modules"; } - if (AllowsNothing) { + if (IsContradictory) { containment = "no Modules"; } - if (!AllowsNothing && !AllowsAnyModule) { + if (!IsContradictory && !AllowsAnyModule) { var count = AllowedModuleNames.Count; if (count == 1) { containment = "Module '" + AllowedModuleNames[0] + "'"; @@ -586,7 +593,7 @@ public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List obj_ids) color = Config.CAGE_EVERYTHING_COLOR; } - if (AllowsNothing || !IsValid) { + if (IsContradictory || !IsValid) { color = Config.CAGE_NONE_COLOR; } diff --git a/bin/Monoceros.pdb b/bin/Monoceros.pdb index ca7cbe7..623dc44 100644 Binary files a/bin/Monoceros.pdb and b/bin/Monoceros.pdb differ