diff --git a/cs/TagsCloudTests/ArchimedeanSpiralPointGenerator_Should.cs b/cs/TagsCloudTests/ArchimedeanSpiralPointGenerator_Should.cs new file mode 100644 index 000000000..3c394c7fa --- /dev/null +++ b/cs/TagsCloudTests/ArchimedeanSpiralPointGenerator_Should.cs @@ -0,0 +1,43 @@ +using FluentAssertions; +using NUnit.Framework; +using SixLabors.ImageSharp; + +namespace TagsCloudTests; + +public class ArchimedeanSpiralPointGenerator_Should : CircularCloudLayouterTestsBase +{ + [Test] + public void GetNextPoint_ShouldReturnFirstPointAtCenter() + { + var point = Generator.GetNextPoint(); + point.Should().Be(Center); + } + + [Test] + public void GetNextPoint_ShouldReturnDifferentPoints() + { + var point1 = Generator.GetNextPoint(); + var point2 = Generator.GetNextPoint(); + point1.Should().NotBe(point2); + } + + [Test] + public void GetNextPoint_ShouldGeneratePointsInSpiralPattern() + { + var previousPoint = Generator.GetNextPoint(); + var previousDistance = Distance(Center, previousPoint); + + for (int i = 0; i < 10; i++) + { + var currentPoint = Generator.GetNextPoint(); + var currentDistance = Distance(Center, currentPoint); + currentDistance.Should().BeGreaterThan(previousDistance); + previousDistance = currentDistance; + } + } + + private float Distance(PointF p1, PointF p2) + { + return (float)Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudTests/CircularCloudLayouterTestsBase.cs b/cs/TagsCloudTests/CircularCloudLayouterTestsBase.cs new file mode 100644 index 000000000..84a4010b0 --- /dev/null +++ b/cs/TagsCloudTests/CircularCloudLayouterTestsBase.cs @@ -0,0 +1,53 @@ +using NUnit.Framework; +using SixLabors.ImageSharp; +using TagsCloudVisualization.CloudLayouter; + +namespace TagsCloudTests; + +[TestFixture] +public abstract class CircularCloudLayouterTestsBase +{ + protected CircularCloudLayouter Layouter; + protected CloudVisualizer Visualizer; + protected ArchimedeanSpiralPointGenerator Generator; + protected SizeF[] RectangleSizes; + protected PointF Center; + protected int MaxDistanceToCenter; + protected string ImagesDirectory; + + [SetUp] + public virtual void SetUp() + { + Center = new PointF(600, 450); + Layouter = new CircularCloudLayouter(Center); + Visualizer = new CloudVisualizer(); + RectangleSizes = + [ + new SizeF(70, 100), + new SizeF(60, 60), + new SizeF(90, 30), + new SizeF(75, 115), + new SizeF(100, 100) + ]; + Generator = new ArchimedeanSpiralPointGenerator(Center); + MaxDistanceToCenter = 120; + var projectDirectory = Directory.GetParent(Environment.CurrentDirectory)?.Parent?.Parent?.FullName + ?? throw new InvalidOperationException("Не удалось определить директорию проекта."); + ImagesDirectory = Path.Combine(projectDirectory, "Images"); + if (!Directory.Exists(ImagesDirectory)) + { + Directory.CreateDirectory(ImagesDirectory); + } + } + + [TearDown] + public virtual void TearDown() + { + if (TestContext.CurrentContext.Result.Outcome.Status != NUnit.Framework.Interfaces.TestStatus.Failed) + return; + var testName = TestContext.CurrentContext.Test.Name; + var filePath = Path.Combine(ImagesDirectory, $"{testName}_failed.png"); + Visualizer.SaveVisualization(Layouter.Rectangles, filePath); + Console.WriteLine($"Tag cloud visualization saved to file {filePath}"); + } +} \ No newline at end of file diff --git a/cs/TagsCloudTests/CircularCloudLayouter_Should.cs b/cs/TagsCloudTests/CircularCloudLayouter_Should.cs new file mode 100644 index 000000000..6c1fab24e --- /dev/null +++ b/cs/TagsCloudTests/CircularCloudLayouter_Should.cs @@ -0,0 +1,109 @@ +using FluentAssertions; +using NUnit.Framework; +using SixLabors.ImageSharp; + +namespace TagsCloudTests; + +[TestFixture] +public class CircularCloudLayouter_Should : CircularCloudLayouterTestsBase +{ + [Test] + public void CircularCloud_ShouldBeEmpty_WhenCreated() + { + Layouter.Rectangles.Should().BeEmpty(); + } + + [TestCase(0, 10)] + [TestCase(10, 0)] + [TestCase(-1, 10)] + [TestCase(10, -1)] + [TestCase(0, 0)] + [TestCase(-10, -10)] + public void CircularCloud_ShouldThrowArgumentException_WhenInvalidRectangleSize(int width, int height) + { + var func = () => Layouter.PutNextRectangle(new SizeF(width, height)); + func.Should().Throw(); + } + + [Test] + public void PutNextRectangle_ShouldReturnRectangleWithSameSize() + { + var rectangleSize = new SizeF(20, 30); + var rectangle = Layouter.PutNextRectangle(rectangleSize); + + rectangle.Size.Should().BeEquivalentTo(rectangleSize); + } + + [Test] + public void PutNextRectangle_ShouldPlaceRectangleInCenter_WhenFirstRectangle() + { + var rectangleSize = new SizeF(10, 10); + var expectedLocation = new PointF(Center.X - rectangleSize.Width / 2, Center.Y - rectangleSize.Height / 2); + var rectangle = Layouter.PutNextRectangle(rectangleSize); + + rectangle.Location.Should().BeEquivalentTo(expectedLocation); + } + + [Test] + public void PutNextRectangle_ShouldNotIntersectWithPreviousRectangles() + { + foreach (var size in RectangleSizes) + { + Layouter.PutNextRectangle(size); + } + VerifyRectanglesDontIntersect(Layouter.Rectangles.ToList()); + } + + [Test] + public void PutNextRectangle_ShouldNotIntersect_WhenPlacingIdenticalRectangles() + { + var rectangleSize = new SizeF(10, 10); + var numberOfRectangles = 10; + + for (int i = 0; i < numberOfRectangles; i++) + { + Layouter.PutNextRectangle(rectangleSize); + } + VerifyRectanglesDontIntersect(Layouter.Rectangles.ToList()); + } + + [Test] + public void CircularCloud_ShouldHaveAllPlacedRectangles() + { + foreach (var size in RectangleSizes) + { + Layouter.PutNextRectangle(size); + } + + Layouter.Rectangles.Should().HaveCount(RectangleSizes.Length); + } + + [Test] + public void CircularCloudRectangles_ShouldBeCloseToCenter() + { + foreach (var size in RectangleSizes) + { + Layouter.PutNextRectangle(size); + } + + var rectangles = Layouter.Rectangles.ToList(); + + foreach (var rectangle in rectangles) + { + var distanceToCenter = Math.Sqrt(Math.Pow(rectangle.X + rectangle.Width / 2 - Center.X, 2) + + Math.Pow(rectangle.Y + rectangle.Height / 2 - Center.Y, 2)); + distanceToCenter.Should().BeLessThan(MaxDistanceToCenter); + } + } + + private void VerifyRectanglesDontIntersect(List rectangles) + { + for (int i = 0; i < rectangles.Count; i++) + { + for (int j = i + 1; j < rectangles.Count; j++) + { + rectangles[i].IntersectsWith(rectangles[j]).Should().BeFalse(); + } + } + } +} \ No newline at end of file diff --git a/cs/TagsCloudTests/CloudVisualizer_Should.cs b/cs/TagsCloudTests/CloudVisualizer_Should.cs new file mode 100644 index 000000000..70efee8af --- /dev/null +++ b/cs/TagsCloudTests/CloudVisualizer_Should.cs @@ -0,0 +1,50 @@ +using FluentAssertions; +using NUnit.Framework; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace TagsCloudTests; + +public class CloudVisualizer_Should : CircularCloudLayouterTestsBase +{ + [Test] + public void SaveVisualization_ShouldCreateImageWithDefaultSize_WhenSmallRectangles() + { + Layouter.PutNextRectangle(new SizeF(10, 10)); + var filePath = Path.Combine(ImagesDirectory, "test_visualization.png"); + Visualizer.SaveVisualization(Layouter.Rectangles, filePath); + + using (var image = Image.Load(filePath)) + { + image.Width.Should().Be(1200); + image.Height.Should().Be(900); + } + File.Delete(filePath); + } + + [Test] + public void SaveVisualization_ShouldCreateImageWithBiggerSize_WhenManyRectangles() + { + for(int i = 0; i < 13; i++) + Layouter.PutNextRectangle(new SizeF(300, 300)); + + var filePath = Path.Combine(ImagesDirectory, "test_visualization.png"); + Visualizer.SaveVisualization(Layouter.Rectangles, filePath); + + using (var image = Image.Load(filePath)) + { + image.Width.Should().BeGreaterThan(1200); + image.Height.Should().BeGreaterThan(900); + } + File.Delete(filePath); + } + + [Test] + public void SaveVisualization_ShouldThrowDirectoryNotFoundException_WhenInvalidPath() + { + var invalidPath = @"M:\NonExistingDirectory\test_visualisation.png"; + var func = () => Visualizer.SaveVisualization(Layouter.Rectangles, invalidPath); + + func.Should().Throw(); + } +} \ No newline at end of file diff --git a/cs/TagsCloudTests/RectangleExtensions_Should.cs b/cs/TagsCloudTests/RectangleExtensions_Should.cs new file mode 100644 index 000000000..96d98482b --- /dev/null +++ b/cs/TagsCloudTests/RectangleExtensions_Should.cs @@ -0,0 +1,51 @@ +using FluentAssertions; +using NUnit.Framework; +using SixLabors.ImageSharp; +using TagsCloudVisualization.CloudLayouter; + +namespace TagsCloudTests; + +[TestFixture] +public class RectangleExtensions_Should : CircularCloudLayouterTestsBase +{ + [Test] + public void IntersectsWithAny_ShouldReturnTrue_WhenRectangleIntersectsWithAnyInCollection() + { + var rectangleToCheck = new RectangleF(50, 50, 100, 100); + var existingRectangles = new List + { + new RectangleF(30, 30, 50, 50), + new RectangleF(200, 200, 50, 50) + }; + + var result = rectangleToCheck.IntersectsWithAny(existingRectangles); + + result.Should().BeTrue(); + } + + [Test] + public void IntersectsWithAny_ShouldReturnFalse_WhenNoIntersectionWithAnyInCollection() + { + var rectangleToCheck = new RectangleF(50, 50, 100, 100); + var existingRectangles = new List + { + new RectangleF(200, 200, 50, 50), + new RectangleF(300, 300, 50, 50) + }; + + var result = rectangleToCheck.IntersectsWithAny(existingRectangles); + + result.Should().BeFalse(); + } + + [Test] + public void IntersectsWithAny_ShouldReturnFalse_WhenCollectionIsEmpty() + { + var rectangleToCheck = new RectangleF(50, 50, 100, 100); + var existingRectangles = new List(); + + var result = rectangleToCheck.IntersectsWithAny(existingRectangles); + + result.Should().BeFalse(); + } +} \ No newline at end of file diff --git a/cs/TagsCloudTests/TagsCloudTests.csproj b/cs/TagsCloudTests/TagsCloudTests.csproj new file mode 100644 index 000000000..77b3ed185 --- /dev/null +++ b/cs/TagsCloudTests/TagsCloudTests.csproj @@ -0,0 +1,22 @@ + + + + Library + net8.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/cs/TagsCloudVisualization/CloudLayouter/ArchimedeanSpiralPointGenerator.cs b/cs/TagsCloudVisualization/CloudLayouter/ArchimedeanSpiralPointGenerator.cs new file mode 100644 index 000000000..fe8ecbb65 --- /dev/null +++ b/cs/TagsCloudVisualization/CloudLayouter/ArchimedeanSpiralPointGenerator.cs @@ -0,0 +1,28 @@ +using SixLabors.ImageSharp; + +namespace TagsCloudVisualization.CloudLayouter; + +public class ArchimedeanSpiralPointGenerator +{ + private readonly PointF center; + private readonly double spiralStep; + private readonly double distanceBetweenTurns; + private double angle; + + public ArchimedeanSpiralPointGenerator(PointF center, double spiralStep = 0.01, double distanceBetweenTurns = 0.01) + { + this.center = center; + this.spiralStep = spiralStep; + this.distanceBetweenTurns = distanceBetweenTurns; + angle = 0; + } + + public PointF GetNextPoint() + { + var radius = distanceBetweenTurns * angle; + var x = (float)(center.X + radius * Math.Cos(angle)); + var y = (float)(center.Y + radius * Math.Sin(angle)); + angle += spiralStep; + return new PointF(x, y); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CloudLayouter/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CloudLayouter/CircularCloudLayouter.cs new file mode 100644 index 000000000..af92f9fc4 --- /dev/null +++ b/cs/TagsCloudVisualization/CloudLayouter/CircularCloudLayouter.cs @@ -0,0 +1,76 @@ +using SixLabors.ImageSharp; +using TagsCloudVisualization.Interfaces; + +namespace TagsCloudVisualization.CloudLayouter; + +public class CircularCloudLayouter : ICloudLayouter +{ + private readonly PointF center; + private readonly ArchimedeanSpiralPointGenerator spiral; + public List Rectangles { get; } + + public CircularCloudLayouter(PointF center) + { + this.center = center; + Rectangles = new List(); + spiral = new ArchimedeanSpiralPointGenerator(center); + } + + public RectangleF PutNextRectangle(SizeF rectangleSize) + { + if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) + throw new ArgumentException("Size should be positive value", nameof(rectangleSize)); + + RectangleF newRectangle; + do + { + var location = spiral.GetNextPoint(); + var rectangleLocation = new PointF(location.X - rectangleSize.Width / 2, + location.Y - rectangleSize.Height / 2); + newRectangle = new RectangleF(rectangleLocation, rectangleSize); + + newRectangle = ShiftRectangleTowardsCenter(newRectangle); + } while (newRectangle.IntersectsWithAny(Rectangles)); + + Rectangles.Add(newRectangle); + return newRectangle; + } + + private RectangleF ShiftRectangleTowardsCenter(RectangleF rectangle) + { + if (Rectangles.Count == 0) + return rectangle; + + var shiftFactor = 0.001f; + var canShiftX = true; + var canShiftY = true; + + while (canShiftX || canShiftY) + { + var centerShift = new PointF(center.X - rectangle.X, center.Y - rectangle.Y); + + var shiftX = canShiftX ? centerShift.X * shiftFactor : 0; + var shiftY = canShiftY ? centerShift.Y * shiftFactor : 0; + + var shiftedRectangle = new RectangleF( + rectangle.X + shiftX, + rectangle.Y + shiftY, + rectangle.Width, + rectangle.Height); + + if (shiftedRectangle.IntersectsWithAny(Rectangles)) + { + if (canShiftX) + canShiftX = false; + if (canShiftY) + canShiftY = false; + } + else + { + rectangle = shiftedRectangle; + } + } + + return rectangle; + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CloudLayouter/CloudVisualizer.cs b/cs/TagsCloudVisualization/CloudLayouter/CloudVisualizer.cs new file mode 100644 index 000000000..c31be31e5 --- /dev/null +++ b/cs/TagsCloudVisualization/CloudLayouter/CloudVisualizer.cs @@ -0,0 +1,37 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace TagsCloudVisualization.CloudLayouter; + +public class CloudVisualizer +{ + public void SaveVisualization(List rectangles, string filePath) + { + var directory = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directory)) + throw new DirectoryNotFoundException("The directory does not exist."); + + var maxWidth = rectangles.Max(r => r.Right); + var maxHeight = rectangles.Max(r => r.Bottom); + + var width = (int)Math.Max(maxWidth, 1200); + var height = (int)Math.Max(maxHeight, 900); + + using (var image = new Image(width, height)) + { + image.Mutate(ctx => + { + ctx.Fill(Color.White); + foreach (var rectangle in rectangles) + { + ctx.Fill(Color.CornflowerBlue, rectangle); + ctx.Draw(Color.Black, 1, rectangle); + } + }); + + image.Save(filePath); + } + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CloudLayouter/RectangleExtensions.cs b/cs/TagsCloudVisualization/CloudLayouter/RectangleExtensions.cs new file mode 100644 index 000000000..9e58f2b00 --- /dev/null +++ b/cs/TagsCloudVisualization/CloudLayouter/RectangleExtensions.cs @@ -0,0 +1,11 @@ +using SixLabors.ImageSharp; + +namespace TagsCloudVisualization.CloudLayouter; + +public static class RectangleExtensions +{ + public static bool IntersectsWithAny(this RectangleF rectangle, IEnumerable rectangles) + { + return rectangles.Any(existingRectangle => existingRectangle.IntersectsWith(rectangle)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Images/Test_ShouldBeFailed_AnyWay_failed.png b/cs/TagsCloudVisualization/Images/Test_ShouldBeFailed_AnyWay_failed.png new file mode 100644 index 000000000..31a2ea653 Binary files /dev/null and b/cs/TagsCloudVisualization/Images/Test_ShouldBeFailed_AnyWay_failed.png differ diff --git a/cs/TagsCloudVisualization/Images/tag_cloud_equals_rectangles.png b/cs/TagsCloudVisualization/Images/tag_cloud_equals_rectangles.png new file mode 100644 index 000000000..65303e1eb Binary files /dev/null and b/cs/TagsCloudVisualization/Images/tag_cloud_equals_rectangles.png differ diff --git a/cs/TagsCloudVisualization/Images/tag_cloud_random_1.png b/cs/TagsCloudVisualization/Images/tag_cloud_random_1.png new file mode 100644 index 000000000..97d9c6ec6 Binary files /dev/null and b/cs/TagsCloudVisualization/Images/tag_cloud_random_1.png differ diff --git a/cs/TagsCloudVisualization/Images/tag_cloud_random_2.png b/cs/TagsCloudVisualization/Images/tag_cloud_random_2.png new file mode 100644 index 000000000..71affba26 Binary files /dev/null and b/cs/TagsCloudVisualization/Images/tag_cloud_random_2.png differ diff --git a/cs/TagsCloudVisualization/Images/tag_cloud_random_3.png b/cs/TagsCloudVisualization/Images/tag_cloud_random_3.png new file mode 100644 index 000000000..c4353a9e1 Binary files /dev/null and b/cs/TagsCloudVisualization/Images/tag_cloud_random_3.png differ diff --git a/cs/TagsCloudVisualization/Interfaces/ICloudLayouter.cs b/cs/TagsCloudVisualization/Interfaces/ICloudLayouter.cs new file mode 100644 index 000000000..2306c2cba --- /dev/null +++ b/cs/TagsCloudVisualization/Interfaces/ICloudLayouter.cs @@ -0,0 +1,8 @@ +using SixLabors.ImageSharp; + +namespace TagsCloudVisualization.Interfaces; + +public interface ICloudLayouter +{ + public RectangleF PutNextRectangle(SizeF rectangleSize); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/README.md b/cs/TagsCloudVisualization/README.md new file mode 100644 index 000000000..0066e734e --- /dev/null +++ b/cs/TagsCloudVisualization/README.md @@ -0,0 +1,11 @@ +# Визуализация рандомных облаков тегов + + +## Первое облако +![Первое облако](Images/tag_cloud_random_1.png) + +## Второе облако +![Второе облако](Images/tag_cloud_random_2.png) + +## Третье облако +![Третье облако](Images/tag_cloud_random_3.png) \ No newline at end of file diff --git a/cs/TagsCloudVisualization/RandomVisualisation.cs b/cs/TagsCloudVisualization/RandomVisualisation.cs new file mode 100644 index 000000000..5a1b765e1 --- /dev/null +++ b/cs/TagsCloudVisualization/RandomVisualisation.cs @@ -0,0 +1,45 @@ +using SixLabors.ImageSharp; +using TagsCloudVisualization.CloudLayouter; + +namespace TagsCloudVisualization; + +class RandomVisualisation +{ + static void Main(string[] args) + { + + var projectDirectory = Directory.GetParent(Environment.CurrentDirectory)?.Parent?.Parent?.FullName + ?? throw new InvalidOperationException("Не удалось определить директорию проекта."); + var imagesDirectory = Path.Combine(projectDirectory, "Images"); + + if (!Directory.Exists(imagesDirectory)) + { + Directory.CreateDirectory(imagesDirectory); + } + var layouter = new CircularCloudLayouter(new PointF(600, 450)); + var random = new Random(); + var visualizer = new CloudVisualizer(); + + var numberOfRectangles = 150; + for (int i = 0; i < numberOfRectangles; i++) + { + var width = 35; + var height = 20; + layouter.PutNextRectangle(new SizeF(width, height)); + } + visualizer.SaveVisualization(layouter.Rectangles, Path.Combine(imagesDirectory, $"tag_cloud_equals_rectangles.png")); + + for (int j = 1; j <= 3; j++) + { + layouter = new CircularCloudLayouter(new PointF(600, 450)); + for (int i = 0; i < numberOfRectangles; i++) + { + var width = random.Next(25, 50); + var height = random.Next(25, 50); + layouter.PutNextRectangle(new SizeF(width, height)); + } + visualizer.SaveVisualization(layouter.Rectangles, Path.Combine(imagesDirectory, $"tag_cloud_random_{j}.png")); + } + Console.WriteLine("Визуализация была сохранена."); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj new file mode 100644 index 000000000..31783865d --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj @@ -0,0 +1,24 @@ + + + + Library + net8.0 + enable + enable + TagsCloudVisualization.RandomVisualisation + + + + + + + + + + + + + + + + diff --git a/cs/tdd.sln b/cs/tdd.sln index c8f523d63..765833fdd 100644 --- a/cs/tdd.sln +++ b/cs/tdd.sln @@ -1,11 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33712.159 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\BowlingGame.csproj", "{AD0F018A-732E-4074-8527-AB2EEC8D0BF3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BowlingGame", "BowlingGame\BowlingGame.csproj", "{AD0F018A-732E-4074-8527-AB2EEC8D0BF3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{4293F3B1-4BDF-4BDD-A270-EEA4AB820ED2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudTests", "TagsCloudTests\TagsCloudTests.csproj", "{D17BCED2-16E2-44E6-9CF0-3C564D4AA40E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,8 +25,19 @@ Global {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU + {4293F3B1-4BDF-4BDD-A270-EEA4AB820ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4293F3B1-4BDF-4BDD-A270-EEA4AB820ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4293F3B1-4BDF-4BDD-A270-EEA4AB820ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4293F3B1-4BDF-4BDD-A270-EEA4AB820ED2}.Release|Any CPU.Build.0 = Release|Any CPU + {D17BCED2-16E2-44E6-9CF0-3C564D4AA40E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D17BCED2-16E2-44E6-9CF0-3C564D4AA40E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D17BCED2-16E2-44E6-9CF0-3C564D4AA40E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D17BCED2-16E2-44E6-9CF0-3C564D4AA40E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1C452E1F-FFE8-4289-BA1C-644300CA10C0} + EndGlobalSection EndGlobal