From b2a723ca60103ac0cc858095c1dc2addd46e0d04 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 8 Apr 2026 07:44:03 -0400 Subject: [PATCH] feat: add Shape Library UI with configurable shapes and flange presets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Shape Library dialog (Nest > Shape Library) for creating drawings from built-in parametric shapes. Supports configuration presets loaded from JSON files — ships with 136 standard pipe flanges. Parameters use TextBox inputs with architectural unit parsing (feet/inches, fractions). - ShapeLibraryForm with split layout: shape list, preview, parameters - ShapePreviewControl for auto-zoom rendering with info overlay - ArchUnits utility for parsing architectural measurements - SetPreviewDefaults() on all ShapeDefinition subclasses - Convention-based config discovery (Configurations/{ShapeName}.json) Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Core/Shapes/CircleShape.cs | 5 + OpenNest.Core/Shapes/FlangeShape.cs | 9 + .../Shapes/IsoscelesTriangleShape.cs | 6 + OpenNest.Core/Shapes/LShape.cs | 8 + OpenNest.Core/Shapes/OctagonShape.cs | 5 + OpenNest.Core/Shapes/RectangleShape.cs | 6 + OpenNest.Core/Shapes/RightTriangleShape.cs | 6 + OpenNest.Core/Shapes/RingShape.cs | 6 + OpenNest.Core/Shapes/RoundedRectangleShape.cs | 7 + OpenNest.Core/Shapes/ShapeDefinition.cs | 2 + OpenNest.Core/Shapes/TShape.cs | 8 + OpenNest.Core/Shapes/TrapezoidShape.cs | 7 + OpenNest/ArchUnits.cs | 84 ++ OpenNest/Configurations/FlangeShape.json | 1090 +++++++++++++++++ OpenNest/Controls/ShapePreviewControl.cs | 79 ++ OpenNest/Forms/MainForm.Designer.cs | 13 +- OpenNest/Forms/MainForm.cs | 14 + OpenNest/Forms/ShapeLibraryForm.Designer.cs | 338 +++++ OpenNest/Forms/ShapeLibraryForm.cs | 322 +++++ OpenNest/Forms/ShapeLibraryForm.resx | 120 ++ OpenNest/OpenNest.csproj | 5 + 21 files changed, 2138 insertions(+), 2 deletions(-) create mode 100644 OpenNest/ArchUnits.cs create mode 100644 OpenNest/Configurations/FlangeShape.json create mode 100644 OpenNest/Controls/ShapePreviewControl.cs create mode 100644 OpenNest/Forms/ShapeLibraryForm.Designer.cs create mode 100644 OpenNest/Forms/ShapeLibraryForm.cs create mode 100644 OpenNest/Forms/ShapeLibraryForm.resx diff --git a/OpenNest.Core/Shapes/CircleShape.cs b/OpenNest.Core/Shapes/CircleShape.cs index 1b3ac64..1375abc 100644 --- a/OpenNest.Core/Shapes/CircleShape.cs +++ b/OpenNest.Core/Shapes/CircleShape.cs @@ -7,6 +7,11 @@ namespace OpenNest.Shapes { public double Diameter { get; set; } + public override void SetPreviewDefaults() + { + Diameter = 8; + } + public override Drawing GetDrawing() { var entities = new List diff --git a/OpenNest.Core/Shapes/FlangeShape.cs b/OpenNest.Core/Shapes/FlangeShape.cs index b08d87f..2eff093 100644 --- a/OpenNest.Core/Shapes/FlangeShape.cs +++ b/OpenNest.Core/Shapes/FlangeShape.cs @@ -11,6 +11,15 @@ namespace OpenNest.Shapes public double HolePatternDiameter { get; set; } public int HoleCount { get; set; } + public override void SetPreviewDefaults() + { + NominalPipeSize = 2; + OD = 7.5; + HoleDiameter = 0.875; + HolePatternDiameter = 5.5; + HoleCount = 8; + } + public override Drawing GetDrawing() { var entities = new List(); diff --git a/OpenNest.Core/Shapes/IsoscelesTriangleShape.cs b/OpenNest.Core/Shapes/IsoscelesTriangleShape.cs index c4d65f3..499f55f 100644 --- a/OpenNest.Core/Shapes/IsoscelesTriangleShape.cs +++ b/OpenNest.Core/Shapes/IsoscelesTriangleShape.cs @@ -8,6 +8,12 @@ namespace OpenNest.Shapes public double Base { get; set; } public double Height { get; set; } + public override void SetPreviewDefaults() + { + Base = 8; + Height = 10; + } + public override Drawing GetDrawing() { var midX = Base / 2.0; diff --git a/OpenNest.Core/Shapes/LShape.cs b/OpenNest.Core/Shapes/LShape.cs index 701e54f..c91c9fd 100644 --- a/OpenNest.Core/Shapes/LShape.cs +++ b/OpenNest.Core/Shapes/LShape.cs @@ -10,6 +10,14 @@ namespace OpenNest.Shapes public double LegWidth { get; set; } public double LegHeight { get; set; } + public override void SetPreviewDefaults() + { + Width = 8; + Height = 10; + LegWidth = 3; + LegHeight = 3; + } + public override Drawing GetDrawing() { var lw = LegWidth > 0 ? LegWidth : Width / 2.0; diff --git a/OpenNest.Core/Shapes/OctagonShape.cs b/OpenNest.Core/Shapes/OctagonShape.cs index c73f392..d0539fb 100644 --- a/OpenNest.Core/Shapes/OctagonShape.cs +++ b/OpenNest.Core/Shapes/OctagonShape.cs @@ -7,6 +7,11 @@ namespace OpenNest.Shapes { public double Width { get; set; } + public override void SetPreviewDefaults() + { + Width = 8; + } + public override Drawing GetDrawing() { var center = Width / 2.0; diff --git a/OpenNest.Core/Shapes/RectangleShape.cs b/OpenNest.Core/Shapes/RectangleShape.cs index dbe62ea..dc7b2e6 100644 --- a/OpenNest.Core/Shapes/RectangleShape.cs +++ b/OpenNest.Core/Shapes/RectangleShape.cs @@ -8,6 +8,12 @@ namespace OpenNest.Shapes public double Length { get; set; } public double Width { get; set; } + public override void SetPreviewDefaults() + { + Length = 12; + Width = 6; + } + public override Drawing GetDrawing() { var entities = new List diff --git a/OpenNest.Core/Shapes/RightTriangleShape.cs b/OpenNest.Core/Shapes/RightTriangleShape.cs index 5aa1c01..7fef22b 100644 --- a/OpenNest.Core/Shapes/RightTriangleShape.cs +++ b/OpenNest.Core/Shapes/RightTriangleShape.cs @@ -8,6 +8,12 @@ namespace OpenNest.Shapes public double Width { get; set; } public double Height { get; set; } + public override void SetPreviewDefaults() + { + Width = 8; + Height = 6; + } + public override Drawing GetDrawing() { var entities = new List diff --git a/OpenNest.Core/Shapes/RingShape.cs b/OpenNest.Core/Shapes/RingShape.cs index 1cb1e66..991ba4d 100644 --- a/OpenNest.Core/Shapes/RingShape.cs +++ b/OpenNest.Core/Shapes/RingShape.cs @@ -8,6 +8,12 @@ namespace OpenNest.Shapes public double OuterDiameter { get; set; } public double InnerDiameter { get; set; } + public override void SetPreviewDefaults() + { + OuterDiameter = 10; + InnerDiameter = 6; + } + public override Drawing GetDrawing() { var entities = new List diff --git a/OpenNest.Core/Shapes/RoundedRectangleShape.cs b/OpenNest.Core/Shapes/RoundedRectangleShape.cs index 6bc7d30..dc5314c 100644 --- a/OpenNest.Core/Shapes/RoundedRectangleShape.cs +++ b/OpenNest.Core/Shapes/RoundedRectangleShape.cs @@ -10,6 +10,13 @@ namespace OpenNest.Shapes public double Width { get; set; } public double Radius { get; set; } + public override void SetPreviewDefaults() + { + Length = 12; + Width = 6; + Radius = 1; + } + public override Drawing GetDrawing() { var r = Radius; diff --git a/OpenNest.Core/Shapes/ShapeDefinition.cs b/OpenNest.Core/Shapes/ShapeDefinition.cs index b0890c4..f078ba5 100644 --- a/OpenNest.Core/Shapes/ShapeDefinition.cs +++ b/OpenNest.Core/Shapes/ShapeDefinition.cs @@ -26,6 +26,8 @@ namespace OpenNest.Shapes public abstract Drawing GetDrawing(); + public virtual void SetPreviewDefaults() { } + public static List LoadFromJson(string path) where T : ShapeDefinition { var json = File.ReadAllText(path); diff --git a/OpenNest.Core/Shapes/TShape.cs b/OpenNest.Core/Shapes/TShape.cs index 4231de0..d889479 100644 --- a/OpenNest.Core/Shapes/TShape.cs +++ b/OpenNest.Core/Shapes/TShape.cs @@ -10,6 +10,14 @@ namespace OpenNest.Shapes public double StemWidth { get; set; } public double BarHeight { get; set; } + public override void SetPreviewDefaults() + { + Width = 10; + Height = 8; + StemWidth = 3; + BarHeight = 3; + } + public override Drawing GetDrawing() { var sw = StemWidth > 0 ? StemWidth : Width / 3.0; diff --git a/OpenNest.Core/Shapes/TrapezoidShape.cs b/OpenNest.Core/Shapes/TrapezoidShape.cs index b50e0a2..7414bc6 100644 --- a/OpenNest.Core/Shapes/TrapezoidShape.cs +++ b/OpenNest.Core/Shapes/TrapezoidShape.cs @@ -9,6 +9,13 @@ namespace OpenNest.Shapes public double BottomWidth { get; set; } public double Height { get; set; } + public override void SetPreviewDefaults() + { + TopWidth = 6; + BottomWidth = 10; + Height = 6; + } + public override Drawing GetDrawing() { var offset = (BottomWidth - TopWidth) / 2.0; diff --git a/OpenNest/ArchUnits.cs b/OpenNest/ArchUnits.cs new file mode 100644 index 0000000..a618bc3 --- /dev/null +++ b/OpenNest/ArchUnits.cs @@ -0,0 +1,84 @@ +using OpenNest.IO.Bom; +using System; +using System.Drawing; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace OpenNest +{ + public static class ArchUnits + { + private static readonly Regex UnitRegex = + new Regex("^(?\\d+\\.?\\d*\\s*')?\\s*(?\\d+\\.?\\d*\\s*\")?$"); + + public static double ParseToInches(string input) + { + if (string.IsNullOrWhiteSpace(input)) + return 0; + + var sb = new StringBuilder(input.Trim().ToLower()); + + sb.Replace("ft", "'"); + sb.Replace("feet", "'"); + sb.Replace("foot", "'"); + sb.Replace("inches", "\""); + sb.Replace("inch", "\""); + sb.Replace("in", "\""); + + input = Fraction.ReplaceFractionsWithDecimals(sb.ToString()); + + var match = UnitRegex.Match(input); + + if (!match.Success) + { + if (!input.Contains("'") && !input.Contains("\"")) + { + if (double.TryParse(input.Trim(), out var plainInches)) + return System.Math.Round(plainInches, 8); + } + + throw new FormatException("Input is not in a valid format."); + } + + var feet = match.Groups["Feet"]; + var inches = match.Groups["Inches"]; + var totalInches = 0.0; + + if (feet.Success) + { + var x = double.Parse(feet.Value.Remove(feet.Length - 1)); + totalInches += x * 12; + } + + if (inches.Success) + { + var x = double.Parse(inches.Value.Remove(inches.Length - 1)); + totalInches += x; + } + + return System.Math.Round(totalInches, 8); + } + + public static double GetLengthInches(TextBox tb) + { + try + { + if (double.TryParse(tb.Text, out var d)) + { + tb.ForeColor = SystemColors.WindowText; + return d; + } + + var x = ParseToInches(tb.Text); + tb.ForeColor = SystemColors.WindowText; + return x; + } + catch + { + tb.ForeColor = Color.Red; + return double.NaN; + } + } + } +} diff --git a/OpenNest/Configurations/FlangeShape.json b/OpenNest/Configurations/FlangeShape.json new file mode 100644 index 0000000..07608d2 --- /dev/null +++ b/OpenNest/Configurations/FlangeShape.json @@ -0,0 +1,1090 @@ +[ + { + "Name": "0.25in-150#", + "NominalPipeSize": 0.25, + "OD": 3.375, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.25, + "HoleCount": 4 + }, + { + "Name": "0.25in-300#", + "NominalPipeSize": 0.25, + "OD": 3.375, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.25, + "HoleCount": 4 + }, + { + "Name": "0.25in-400#", + "NominalPipeSize": 0.25, + "OD": 3.375, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.25, + "HoleCount": 4 + }, + { + "Name": "0.25in-600#", + "NominalPipeSize": 0.25, + "OD": 3.375, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.25, + "HoleCount": 4 + }, + { + "Name": "0.5in-150#", + "NominalPipeSize": 0.5, + "OD": 3.5, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.38, + "HoleCount": 4 + }, + { + "Name": "0.5in-300#", + "NominalPipeSize": 0.5, + "OD": 3.75, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.63, + "HoleCount": 4 + }, + { + "Name": "0.5in-400#", + "NominalPipeSize": 0.5, + "OD": 3.75, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.63, + "HoleCount": 4 + }, + { + "Name": "0.5in-600#", + "NominalPipeSize": 0.5, + "OD": 3.75, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.63, + "HoleCount": 4 + }, + { + "Name": "0.5in-900#", + "NominalPipeSize": 0.5, + "OD": 4.75, + "HoleDiameter": 0.88, + "HolePatternDiameter": 3.25, + "HoleCount": 4 + }, + { + "Name": "0.5in-1500#", + "NominalPipeSize": 0.5, + "OD": 4.75, + "HoleDiameter": 0.88, + "HolePatternDiameter": 3.25, + "HoleCount": 4 + }, + { + "Name": "0.5in-2500#", + "NominalPipeSize": 0.5, + "OD": 5.25, + "HoleDiameter": 0.88, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "0.75in-150#", + "NominalPipeSize": 0.75, + "OD": 3.875, + "HoleDiameter": 0.62, + "HolePatternDiameter": 2.75, + "HoleCount": 4 + }, + { + "Name": "0.75in-300#", + "NominalPipeSize": 0.75, + "OD": 4.625, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.25, + "HoleCount": 4 + }, + { + "Name": "0.75in-400#", + "NominalPipeSize": 0.75, + "OD": 4.625, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.25, + "HoleCount": 4 + }, + { + "Name": "0.75in-600#", + "NominalPipeSize": 0.75, + "OD": 4.625, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.25, + "HoleCount": 4 + }, + { + "Name": "0.75in-900#", + "NominalPipeSize": 0.75, + "OD": 5.125, + "HoleDiameter": 0.88, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "0.75in-1500#", + "NominalPipeSize": 0.75, + "OD": 5.125, + "HoleDiameter": 0.88, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "0.75in-2500#", + "NominalPipeSize": 0.75, + "OD": 5.5, + "HoleDiameter": 0.88, + "HolePatternDiameter": 3.75, + "HoleCount": 4 + }, + { + "Name": "1in-150#", + "NominalPipeSize": 1.0, + "OD": 4.25, + "HoleDiameter": 0.62, + "HolePatternDiameter": 3.13, + "HoleCount": 4 + }, + { + "Name": "1in-300#", + "NominalPipeSize": 1.0, + "OD": 4.875, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "1in-400#", + "NominalPipeSize": 1.0, + "OD": 4.875, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "1in-600#", + "NominalPipeSize": 1.0, + "OD": 4.875, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "1in-900#", + "NominalPipeSize": 1.0, + "OD": 5.875, + "HoleDiameter": 1.0, + "HolePatternDiameter": 4.0, + "HoleCount": 4 + }, + { + "Name": "1in-1500#", + "NominalPipeSize": 1.0, + "OD": 5.875, + "HoleDiameter": 1.0, + "HolePatternDiameter": 4.0, + "HoleCount": 4 + }, + { + "Name": "1in-2500#", + "NominalPipeSize": 1.0, + "OD": 6.25, + "HoleDiameter": 1.0, + "HolePatternDiameter": 4.25, + "HoleCount": 4 + }, + { + "Name": "1.25in-150#", + "NominalPipeSize": 1.25, + "OD": 4.625, + "HoleDiameter": 0.62, + "HolePatternDiameter": 3.5, + "HoleCount": 4 + }, + { + "Name": "1.25in-300#", + "NominalPipeSize": 1.25, + "OD": 5.25, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.88, + "HoleCount": 4 + }, + { + "Name": "1.25in-400#", + "NominalPipeSize": 1.25, + "OD": 5.25, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.88, + "HoleCount": 4 + }, + { + "Name": "1.25in-600#", + "NominalPipeSize": 1.25, + "OD": 5.25, + "HoleDiameter": 0.75, + "HolePatternDiameter": 3.88, + "HoleCount": 4 + }, + { + "Name": "1.25in-900#", + "NominalPipeSize": 1.25, + "OD": 6.25, + "HoleDiameter": 1.0, + "HolePatternDiameter": 4.38, + "HoleCount": 4 + }, + { + "Name": "1.25in-1500#", + "NominalPipeSize": 1.25, + "OD": 6.25, + "HoleDiameter": 1.0, + "HolePatternDiameter": 4.38, + "HoleCount": 4 + }, + { + "Name": "1.25in-2500#", + "NominalPipeSize": 1.25, + "OD": 7.25, + "HoleDiameter": 1.12, + "HolePatternDiameter": 5.13, + "HoleCount": 4 + }, + { + "Name": "1.5in-150#", + "NominalPipeSize": 1.5, + "OD": 5.0, + "HoleDiameter": 0.62, + "HolePatternDiameter": 3.88, + "HoleCount": 4 + }, + { + "Name": "1.5in-300#", + "NominalPipeSize": 1.5, + "OD": 6.125, + "HoleDiameter": 0.88, + "HolePatternDiameter": 4.5, + "HoleCount": 4 + }, + { + "Name": "1.5in-400#", + "NominalPipeSize": 1.5, + "OD": 6.125, + "HoleDiameter": 0.88, + "HolePatternDiameter": 4.5, + "HoleCount": 4 + }, + { + "Name": "1.5in-600#", + "NominalPipeSize": 1.5, + "OD": 6.125, + "HoleDiameter": 0.88, + "HolePatternDiameter": 4.5, + "HoleCount": 4 + }, + { + "Name": "1.5in-900#", + "NominalPipeSize": 1.5, + "OD": 7.0, + "HoleDiameter": 1.12, + "HolePatternDiameter": 4.88, + "HoleCount": 4 + }, + { + "Name": "1.5in-1500#", + "NominalPipeSize": 1.5, + "OD": 7.0, + "HoleDiameter": 1.12, + "HolePatternDiameter": 4.88, + "HoleCount": 4 + }, + { + "Name": "1.5in-2500#", + "NominalPipeSize": 1.5, + "OD": 8.0, + "HoleDiameter": 1.25, + "HolePatternDiameter": 5.75, + "HoleCount": 4 + }, + { + "Name": "2in-150#", + "NominalPipeSize": 2.0, + "OD": 6.0, + "HoleDiameter": 0.75, + "HolePatternDiameter": 4.75, + "HoleCount": 4 + }, + { + "Name": "2in-300#", + "NominalPipeSize": 2.0, + "OD": 6.5, + "HoleDiameter": 0.75, + "HolePatternDiameter": 5.0, + "HoleCount": 8 + }, + { + "Name": "2in-400#", + "NominalPipeSize": 2.0, + "OD": 6.5, + "HoleDiameter": 0.75, + "HolePatternDiameter": 5.0, + "HoleCount": 8 + }, + { + "Name": "2in-600#", + "NominalPipeSize": 2.0, + "OD": 6.5, + "HoleDiameter": 0.75, + "HolePatternDiameter": 5.0, + "HoleCount": 8 + }, + { + "Name": "2in-900#", + "NominalPipeSize": 2.0, + "OD": 8.5, + "HoleDiameter": 1.0, + "HolePatternDiameter": 6.5, + "HoleCount": 8 + }, + { + "Name": "2in-1500#", + "NominalPipeSize": 2.0, + "OD": 8.5, + "HoleDiameter": 1.0, + "HolePatternDiameter": 6.5, + "HoleCount": 8 + }, + { + "Name": "2in-2500#", + "NominalPipeSize": 2.0, + "OD": 9.25, + "HoleDiameter": 1.12, + "HolePatternDiameter": 6.75, + "HoleCount": 8 + }, + { + "Name": "2.5in-150#", + "NominalPipeSize": 2.5, + "OD": 7.0, + "HoleDiameter": 0.75, + "HolePatternDiameter": 5.5, + "HoleCount": 4 + }, + { + "Name": "2.5in-300#", + "NominalPipeSize": 2.5, + "OD": 7.5, + "HoleDiameter": 0.88, + "HolePatternDiameter": 5.88, + "HoleCount": 8 + }, + { + "Name": "2.5in-400#", + "NominalPipeSize": 2.5, + "OD": 7.5, + "HoleDiameter": 0.88, + "HolePatternDiameter": 5.88, + "HoleCount": 8 + }, + { + "Name": "2.5in-600#", + "NominalPipeSize": 2.5, + "OD": 7.5, + "HoleDiameter": 0.88, + "HolePatternDiameter": 5.88, + "HoleCount": 8 + }, + { + "Name": "2.5in-900#", + "NominalPipeSize": 2.5, + "OD": 9.625, + "HoleDiameter": 1.12, + "HolePatternDiameter": 7.5, + "HoleCount": 8 + }, + { + "Name": "2.5in-1500#", + "NominalPipeSize": 2.5, + "OD": 9.625, + "HoleDiameter": 1.12, + "HolePatternDiameter": 7.5, + "HoleCount": 8 + }, + { + "Name": "2.5in-2500#", + "NominalPipeSize": 2.5, + "OD": 10.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 7.75, + "HoleCount": 8 + }, + { + "Name": "3in-150#", + "NominalPipeSize": 3.0, + "OD": 7.5, + "HoleDiameter": 0.75, + "HolePatternDiameter": 6.0, + "HoleCount": 4 + }, + { + "Name": "3in-300#", + "NominalPipeSize": 3.0, + "OD": 8.25, + "HoleDiameter": 0.88, + "HolePatternDiameter": 6.63, + "HoleCount": 8 + }, + { + "Name": "3in-400#", + "NominalPipeSize": 3.0, + "OD": 8.25, + "HoleDiameter": 0.88, + "HolePatternDiameter": 6.63, + "HoleCount": 8 + }, + { + "Name": "3in-600#", + "NominalPipeSize": 3.0, + "OD": 8.25, + "HoleDiameter": 0.88, + "HolePatternDiameter": 6.63, + "HoleCount": 8 + }, + { + "Name": "3in-900#", + "NominalPipeSize": 3.0, + "OD": 9.5, + "HoleDiameter": 1.0, + "HolePatternDiameter": 7.5, + "HoleCount": 8 + }, + { + "Name": "3in-1500#", + "NominalPipeSize": 3.0, + "OD": 10.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 8.0, + "HoleCount": 8 + }, + { + "Name": "3in-2500#", + "NominalPipeSize": 3.0, + "OD": 12.0, + "HoleDiameter": 1.38, + "HolePatternDiameter": 9.0, + "HoleCount": 8 + }, + { + "Name": "3.5in-150#", + "NominalPipeSize": 3.5, + "OD": 8.5, + "HoleDiameter": 0.75, + "HolePatternDiameter": 7.0, + "HoleCount": 8 + }, + { + "Name": "3.5in-300#", + "NominalPipeSize": 3.5, + "OD": 9.0, + "HoleDiameter": 0.88, + "HolePatternDiameter": 7.25, + "HoleCount": 8 + }, + { + "Name": "3.5in-400#", + "NominalPipeSize": 3.5, + "OD": 9.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 7.25, + "HoleCount": 8 + }, + { + "Name": "3.5in-600#", + "NominalPipeSize": 3.5, + "OD": 9.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 7.25, + "HoleCount": 8 + }, + { + "Name": "4in-150#", + "NominalPipeSize": 4.0, + "OD": 9.0, + "HoleDiameter": 0.75, + "HolePatternDiameter": 7.5, + "HoleCount": 8 + }, + { + "Name": "4in-300#", + "NominalPipeSize": 4.0, + "OD": 10.0, + "HoleDiameter": 0.88, + "HolePatternDiameter": 7.88, + "HoleCount": 8 + }, + { + "Name": "4in-400#", + "NominalPipeSize": 4.0, + "OD": 10.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 7.88, + "HoleCount": 8 + }, + { + "Name": "4in-600#", + "NominalPipeSize": 4.0, + "OD": 10.75, + "HoleDiameter": 1.0, + "HolePatternDiameter": 8.5, + "HoleCount": 8 + }, + { + "Name": "4in-900#", + "NominalPipeSize": 4.0, + "OD": 11.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 9.25, + "HoleCount": 8 + }, + { + "Name": "4in-1500#", + "NominalPipeSize": 4.0, + "OD": 12.25, + "HoleDiameter": 1.38, + "HolePatternDiameter": 9.5, + "HoleCount": 8 + }, + { + "Name": "4in-2500#", + "NominalPipeSize": 4.0, + "OD": 14.0, + "HoleDiameter": 1.62, + "HolePatternDiameter": 10.75, + "HoleCount": 8 + }, + { + "Name": "5in-150#", + "NominalPipeSize": 5.0, + "OD": 10.0, + "HoleDiameter": 0.88, + "HolePatternDiameter": 8.5, + "HoleCount": 8 + }, + { + "Name": "5in-300#", + "NominalPipeSize": 5.0, + "OD": 11.0, + "HoleDiameter": 0.88, + "HolePatternDiameter": 9.25, + "HoleCount": 8 + }, + { + "Name": "5in-400#", + "NominalPipeSize": 5.0, + "OD": 11.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 9.25, + "HoleCount": 8 + }, + { + "Name": "5in-600#", + "NominalPipeSize": 5.0, + "OD": 13.0, + "HoleDiameter": 1.12, + "HolePatternDiameter": 10.5, + "HoleCount": 8 + }, + { + "Name": "5in-900#", + "NominalPipeSize": 5.0, + "OD": 13.75, + "HoleDiameter": 1.38, + "HolePatternDiameter": 11.0, + "HoleCount": 8 + }, + { + "Name": "5in-1500#", + "NominalPipeSize": 5.0, + "OD": 14.75, + "HoleDiameter": 1.62, + "HolePatternDiameter": 11.5, + "HoleCount": 8 + }, + { + "Name": "5in-2500#", + "NominalPipeSize": 5.0, + "OD": 16.5, + "HoleDiameter": 1.88, + "HolePatternDiameter": 12.75, + "HoleCount": 8 + }, + { + "Name": "6in-150#", + "NominalPipeSize": 6.0, + "OD": 11.0, + "HoleDiameter": 0.88, + "HolePatternDiameter": 9.5, + "HoleCount": 8 + }, + { + "Name": "6in-300#", + "NominalPipeSize": 6.0, + "OD": 12.5, + "HoleDiameter": 0.88, + "HolePatternDiameter": 10.63, + "HoleCount": 12 + }, + { + "Name": "6in-400#", + "NominalPipeSize": 6.0, + "OD": 12.5, + "HoleDiameter": 1.0, + "HolePatternDiameter": 10.63, + "HoleCount": 12 + }, + { + "Name": "6in-600#", + "NominalPipeSize": 6.0, + "OD": 14.0, + "HoleDiameter": 1.12, + "HolePatternDiameter": 11.5, + "HoleCount": 12 + }, + { + "Name": "6in-900#", + "NominalPipeSize": 6.0, + "OD": 15.0, + "HoleDiameter": 1.25, + "HolePatternDiameter": 12.5, + "HoleCount": 12 + }, + { + "Name": "6in-1500#", + "NominalPipeSize": 6.0, + "OD": 15.5, + "HoleDiameter": 1.5, + "HolePatternDiameter": 12.5, + "HoleCount": 12 + }, + { + "Name": "6in-2500#", + "NominalPipeSize": 6.0, + "OD": 19.0, + "HoleDiameter": 2.12, + "HolePatternDiameter": 14.5, + "HoleCount": 8 + }, + { + "Name": "8in-150#", + "NominalPipeSize": 8.0, + "OD": 13.5, + "HoleDiameter": 0.88, + "HolePatternDiameter": 11.75, + "HoleCount": 8 + }, + { + "Name": "8in-300#", + "NominalPipeSize": 8.0, + "OD": 15.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 13.0, + "HoleCount": 12 + }, + { + "Name": "8in-400#", + "NominalPipeSize": 8.0, + "OD": 15.0, + "HoleDiameter": 1.12, + "HolePatternDiameter": 13.0, + "HoleCount": 12 + }, + { + "Name": "8in-600#", + "NominalPipeSize": 8.0, + "OD": 16.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 13.75, + "HoleCount": 12 + }, + { + "Name": "8in-900#", + "NominalPipeSize": 8.0, + "OD": 18.5, + "HoleDiameter": 1.5, + "HolePatternDiameter": 15.5, + "HoleCount": 12 + }, + { + "Name": "8in-1500#", + "NominalPipeSize": 8.0, + "OD": 19.0, + "HoleDiameter": 1.75, + "HolePatternDiameter": 15.5, + "HoleCount": 12 + }, + { + "Name": "8in-2500#", + "NominalPipeSize": 8.0, + "OD": 21.75, + "HoleDiameter": 2.12, + "HolePatternDiameter": 17.25, + "HoleCount": 12 + }, + { + "Name": "10in-150#", + "NominalPipeSize": 10.0, + "OD": 16.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 14.25, + "HoleCount": 12 + }, + { + "Name": "10in-300#", + "NominalPipeSize": 10.0, + "OD": 17.5, + "HoleDiameter": 1.12, + "HolePatternDiameter": 15.25, + "HoleCount": 16 + }, + { + "Name": "10in-400#", + "NominalPipeSize": 10.0, + "OD": 17.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 15.25, + "HoleCount": 16 + }, + { + "Name": "10in-600#", + "NominalPipeSize": 10.0, + "OD": 20.0, + "HoleDiameter": 1.38, + "HolePatternDiameter": 17.0, + "HoleCount": 16 + }, + { + "Name": "10in-900#", + "NominalPipeSize": 10.0, + "OD": 21.5, + "HoleDiameter": 1.5, + "HolePatternDiameter": 18.5, + "HoleCount": 16 + }, + { + "Name": "10in-1500#", + "NominalPipeSize": 10.0, + "OD": 23.0, + "HoleDiameter": 2.0, + "HolePatternDiameter": 19.0, + "HoleCount": 12 + }, + { + "Name": "10in-2500#", + "NominalPipeSize": 10.0, + "OD": 26.5, + "HoleDiameter": 2.62, + "HolePatternDiameter": 21.25, + "HoleCount": 12 + }, + { + "Name": "12in-150#", + "NominalPipeSize": 12.0, + "OD": 19.0, + "HoleDiameter": 1.0, + "HolePatternDiameter": 17.0, + "HoleCount": 12 + }, + { + "Name": "12in-300#", + "NominalPipeSize": 12.0, + "OD": 20.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 17.75, + "HoleCount": 16 + }, + { + "Name": "12in-400#", + "NominalPipeSize": 12.0, + "OD": 20.5, + "HoleDiameter": 1.38, + "HolePatternDiameter": 17.75, + "HoleCount": 16 + }, + { + "Name": "12in-600#", + "NominalPipeSize": 12.0, + "OD": 22.0, + "HoleDiameter": 1.38, + "HolePatternDiameter": 19.25, + "HoleCount": 20 + }, + { + "Name": "12in-900#", + "NominalPipeSize": 12.0, + "OD": 24.0, + "HoleDiameter": 1.5, + "HolePatternDiameter": 21.0, + "HoleCount": 20 + }, + { + "Name": "12in-1500#", + "NominalPipeSize": 12.0, + "OD": 26.5, + "HoleDiameter": 2.12, + "HolePatternDiameter": 22.5, + "HoleCount": 16 + }, + { + "Name": "12in-2500#", + "NominalPipeSize": 12.0, + "OD": 30.0, + "HoleDiameter": 2.88, + "HolePatternDiameter": 24.375, + "HoleCount": 12 + }, + { + "Name": "14in-150#", + "NominalPipeSize": 14.0, + "OD": 21.0, + "HoleDiameter": 1.12, + "HolePatternDiameter": 18.75, + "HoleCount": 12 + }, + { + "Name": "14in-300#", + "NominalPipeSize": 14.0, + "OD": 23.0, + "HoleDiameter": 1.25, + "HolePatternDiameter": 20.25, + "HoleCount": 20 + }, + { + "Name": "14in-400#", + "NominalPipeSize": 14.0, + "OD": 23.0, + "HoleDiameter": 1.38, + "HolePatternDiameter": 20.25, + "HoleCount": 20 + }, + { + "Name": "14in-600#", + "NominalPipeSize": 14.0, + "OD": 23.75, + "HoleDiameter": 1.5, + "HolePatternDiameter": 20.75, + "HoleCount": 20 + }, + { + "Name": "14in-900#", + "NominalPipeSize": 14.0, + "OD": 25.25, + "HoleDiameter": 1.62, + "HolePatternDiameter": 22.0, + "HoleCount": 20 + }, + { + "Name": "14in-1500#", + "NominalPipeSize": 14.0, + "OD": 29.5, + "HoleDiameter": 2.38, + "HolePatternDiameter": 25.0, + "HoleCount": 16 + }, + { + "Name": "16in-150#", + "NominalPipeSize": 16.0, + "OD": 23.5, + "HoleDiameter": 1.12, + "HolePatternDiameter": 21.25, + "HoleCount": 16 + }, + { + "Name": "16in-300#", + "NominalPipeSize": 16.0, + "OD": 25.5, + "HoleDiameter": 1.38, + "HolePatternDiameter": 22.5, + "HoleCount": 20 + }, + { + "Name": "16in-400#", + "NominalPipeSize": 16.0, + "OD": 25.5, + "HoleDiameter": 1.5, + "HolePatternDiameter": 22.5, + "HoleCount": 20 + }, + { + "Name": "16in-600#", + "NominalPipeSize": 16.0, + "OD": 27.0, + "HoleDiameter": 1.62, + "HolePatternDiameter": 23.75, + "HoleCount": 20 + }, + { + "Name": "16in-900#", + "NominalPipeSize": 16.0, + "OD": 27.75, + "HoleDiameter": 1.75, + "HolePatternDiameter": 24.5, + "HoleCount": 20 + }, + { + "Name": "16in-1500#", + "NominalPipeSize": 16.0, + "OD": 32.5, + "HoleDiameter": 2.62, + "HolePatternDiameter": 27.75, + "HoleCount": 16 + }, + { + "Name": "18in-150#", + "NominalPipeSize": 18.0, + "OD": 25.0, + "HoleDiameter": 1.25, + "HolePatternDiameter": 22.75, + "HoleCount": 16 + }, + { + "Name": "18in-300#", + "NominalPipeSize": 18.0, + "OD": 28.0, + "HoleDiameter": 1.38, + "HolePatternDiameter": 24.75, + "HoleCount": 24 + }, + { + "Name": "18in-400#", + "NominalPipeSize": 18.0, + "OD": 28.0, + "HoleDiameter": 1.5, + "HolePatternDiameter": 24.75, + "HoleCount": 24 + }, + { + "Name": "18in-600#", + "NominalPipeSize": 18.0, + "OD": 29.25, + "HoleDiameter": 1.75, + "HolePatternDiameter": 25.75, + "HoleCount": 20 + }, + { + "Name": "18in-900#", + "NominalPipeSize": 18.0, + "OD": 31.0, + "HoleDiameter": 2.0, + "HolePatternDiameter": 27.0, + "HoleCount": 20 + }, + { + "Name": "18in-1500#", + "NominalPipeSize": 18.0, + "OD": 36.0, + "HoleDiameter": 2.88, + "HolePatternDiameter": 30.5, + "HoleCount": 16 + }, + { + "Name": "20in-150#", + "NominalPipeSize": 20.0, + "OD": 27.5, + "HoleDiameter": 1.25, + "HolePatternDiameter": 25.0, + "HoleCount": 20 + }, + { + "Name": "20in-300#", + "NominalPipeSize": 20.0, + "OD": 30.5, + "HoleDiameter": 1.38, + "HolePatternDiameter": 27.0, + "HoleCount": 24 + }, + { + "Name": "20in-400#", + "NominalPipeSize": 20.0, + "OD": 30.5, + "HoleDiameter": 1.62, + "HolePatternDiameter": 27.0, + "HoleCount": 24 + }, + { + "Name": "20in-600#", + "NominalPipeSize": 20.0, + "OD": 32.0, + "HoleDiameter": 1.75, + "HolePatternDiameter": 28.5, + "HoleCount": 24 + }, + { + "Name": "20in-900#", + "NominalPipeSize": 20.0, + "OD": 33.75, + "HoleDiameter": 2.12, + "HolePatternDiameter": 29.5, + "HoleCount": 20 + }, + { + "Name": "20in-1500#", + "NominalPipeSize": 20.0, + "OD": 38.75, + "HoleDiameter": 3.12, + "HolePatternDiameter": 32.75, + "HoleCount": 16 + }, + { + "Name": "24in-150#", + "NominalPipeSize": 24.0, + "OD": 32.0, + "HoleDiameter": 1.38, + "HolePatternDiameter": 29.5, + "HoleCount": 20 + }, + { + "Name": "24in-300#", + "NominalPipeSize": 24.0, + "OD": 36.0, + "HoleDiameter": 1.62, + "HolePatternDiameter": 32.0, + "HoleCount": 24 + }, + { + "Name": "24in-400#", + "NominalPipeSize": 24.0, + "OD": 36.0, + "HoleDiameter": 1.88, + "HolePatternDiameter": 32.0, + "HoleCount": 24 + }, + { + "Name": "24in-600#", + "NominalPipeSize": 24.0, + "OD": 37.0, + "HoleDiameter": 2.0, + "HolePatternDiameter": 33.0, + "HoleCount": 24 + }, + { + "Name": "24in-900#", + "NominalPipeSize": 24.0, + "OD": 41.0, + "HoleDiameter": 2.62, + "HolePatternDiameter": 35.5, + "HoleCount": 20 + }, + { + "Name": "24in-1500#", + "NominalPipeSize": 24.0, + "OD": 46.0, + "HoleDiameter": 3.62, + "HolePatternDiameter": 39.0, + "HoleCount": 16 + } +] \ No newline at end of file diff --git a/OpenNest/Controls/ShapePreviewControl.cs b/OpenNest/Controls/ShapePreviewControl.cs new file mode 100644 index 0000000..6ddde19 --- /dev/null +++ b/OpenNest/Controls/ShapePreviewControl.cs @@ -0,0 +1,79 @@ +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace OpenNest.Controls +{ + public class ShapePreviewControl : PlateView + { + private string[] infoLines; + + public ShapePreviewControl() + { + DrawOrigin = false; + DrawBounds = false; + AllowPan = false; + AllowSelect = false; + AllowZoom = false; + AllowDrop = false; + BackColor = Color.White; + } + + public void SetInfo(params string[] lines) + { + infoLines = lines; + Invalidate(); + } + + public void ShowDrawing(Drawing drawing) + { + Plate.Parts.Clear(); + Plate.Size = new Geometry.Size(0, 0); + + if (drawing?.Program != null) + { + AddPartFromDrawing(drawing, Geometry.Vector.Zero); + ZoomToFit(); + } + else + { + Invalidate(); + } + } + + protected override void OnResize(System.EventArgs e) + { + base.OnResize(e); + + if (Plate.Parts.Count > 0) + ZoomToFit(false); + } + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + + e.Graphics.TranslateTransform(origin.X, origin.Y); + Renderer.DrawPlate(e.Graphics); + Renderer.DrawParts(e.Graphics); + e.Graphics.ResetTransform(); + + PaintInfo(e.Graphics); + } + + private void PaintInfo(Graphics g) + { + if (infoLines == null) return; + + var lineHeight = Font.GetHeight(g) + 1; + var y = 4f; + + foreach (var line in infoLines) + { + if (string.IsNullOrEmpty(line)) continue; + g.DrawString(line, Font, Brushes.Black, 4, y); + y += lineHeight; + } + } + } +} diff --git a/OpenNest/Forms/MainForm.Designer.cs b/OpenNest/Forms/MainForm.Designer.cs index e1e7a33..8231644 100644 --- a/OpenNest/Forms/MainForm.Designer.cs +++ b/OpenNest/Forms/MainForm.Designer.cs @@ -85,6 +85,7 @@ mnuNest = new System.Windows.Forms.ToolStripMenuItem(); mnuNestEdit = new System.Windows.Forms.ToolStripMenuItem(); mnuNestImportDrawing = new System.Windows.Forms.ToolStripMenuItem(); + mnuNestShapeLibrary = new System.Windows.Forms.ToolStripMenuItem(); toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator(); mnuNestFirstPlate = new System.Windows.Forms.ToolStripMenuItem(); mnuNestLastPlate = new System.Windows.Forms.ToolStripMenuItem(); @@ -559,7 +560,7 @@ // // mnuNest // - mnuNest.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuNestEdit, mnuNestImportDrawing, toolStripMenuItem7, mnuNestFirstPlate, mnuNestLastPlate, toolStripMenuItem6, mnuNestNextPlate, mnuNestPreviousPlate, toolStripMenuItem12, runAutoNestToolStripMenuItem, autoSequenceAllPlatesToolStripMenuItem, mnuNestRemoveEmptyPlates, mnuNestPost, toolStripMenuItem19, calculateCutTimeToolStripMenuItem, toolStripMenuItem22, mnuNestAssignLeadIns, mnuNestRemoveLeadIns }); + mnuNest.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuNestEdit, mnuNestImportDrawing, mnuNestShapeLibrary, toolStripMenuItem7, mnuNestFirstPlate, mnuNestLastPlate, toolStripMenuItem6, mnuNestNextPlate, mnuNestPreviousPlate, toolStripMenuItem12, runAutoNestToolStripMenuItem, autoSequenceAllPlatesToolStripMenuItem, mnuNestRemoveEmptyPlates, mnuNestPost, toolStripMenuItem19, calculateCutTimeToolStripMenuItem, toolStripMenuItem22, mnuNestAssignLeadIns, mnuNestRemoveLeadIns }); mnuNest.Name = "mnuNest"; mnuNest.Size = new System.Drawing.Size(43, 20); mnuNest.Text = "&Nest"; @@ -578,7 +579,14 @@ mnuNestImportDrawing.Size = new System.Drawing.Size(205, 22); mnuNestImportDrawing.Text = "Import Drawing"; mnuNestImportDrawing.Click += Import_Click; - // + // + // mnuNestShapeLibrary + // + mnuNestShapeLibrary.Name = "mnuNestShapeLibrary"; + mnuNestShapeLibrary.Size = new System.Drawing.Size(205, 22); + mnuNestShapeLibrary.Text = "Shape Library"; + mnuNestShapeLibrary.Click += ShapeLibrary_Click; + // // toolStripMenuItem7 // toolStripMenuItem7.Name = "toolStripMenuItem7"; @@ -1213,6 +1221,7 @@ private System.Windows.Forms.ToolStripMenuItem mnuNest; private System.Windows.Forms.ToolStripMenuItem mnuNestEdit; private System.Windows.Forms.ToolStripMenuItem mnuNestImportDrawing; + private System.Windows.Forms.ToolStripMenuItem mnuNestShapeLibrary; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7; private System.Windows.Forms.ToolStripMenuItem mnuNestFirstPlate; private System.Windows.Forms.ToolStripMenuItem mnuNestLastPlate; diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index b07a239..4f4d5b4 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -829,6 +829,20 @@ namespace OpenNest.Forms activeForm.Import(); } + private void ShapeLibrary_Click(object sender, EventArgs e) + { + if (activeForm == null) return; + + var form = new ShapeLibraryForm(); + form.ShowDialog(); + + var drawings = form.GetDrawings(); + if (drawings.Count == 0) return; + + drawings.ForEach(d => activeForm.Nest.Drawings.Add(d)); + activeForm.UpdateDrawingList(); + } + private void EditNest_Click(object sender, EventArgs e) { if (activeForm == null) return; diff --git a/OpenNest/Forms/ShapeLibraryForm.Designer.cs b/OpenNest/Forms/ShapeLibraryForm.Designer.cs new file mode 100644 index 0000000..93e2ef6 --- /dev/null +++ b/OpenNest/Forms/ShapeLibraryForm.Designer.cs @@ -0,0 +1,338 @@ +namespace OpenNest.Forms +{ + partial class ShapeLibraryForm + { + private System.ComponentModel.IContainer components = null; + + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + private void InitializeComponent() + { + ColorScheme colorScheme1 = new ColorScheme(); + CutOffSettings cutOffSettings1 = new CutOffSettings(); + Plate plate1 = new Plate(); + Collections.ObservableList observableList_11 = new Collections.ObservableList(); + Collections.ObservableList observableList_12 = new Collections.ObservableList(); + splitContainer = new System.Windows.Forms.SplitContainer(); + shapeListBox = new System.Windows.Forms.ListBox(); + layoutTable = new System.Windows.Forms.TableLayoutPanel(); + fieldsTable = new System.Windows.Forms.TableLayoutPanel(); + nameLabel = new System.Windows.Forms.Label(); + nameTextBox = new System.Windows.Forms.TextBox(); + qtyLabel = new System.Windows.Forms.Label(); + quantityUpDown = new OpenNest.Controls.NumericUpDown(); + configLabel = new System.Windows.Forms.Label(); + configComboBox = new System.Windows.Forms.ComboBox(); + contentPanel = new System.Windows.Forms.Panel(); + previewBox = new OpenNest.Controls.ShapePreviewControl(); + parametersPanel = new System.Windows.Forms.Panel(); + buttonPanel = new System.Windows.Forms.Panel(); + addButton = new System.Windows.Forms.Button(); + closeButton = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)splitContainer).BeginInit(); + splitContainer.Panel1.SuspendLayout(); + splitContainer.Panel2.SuspendLayout(); + splitContainer.SuspendLayout(); + layoutTable.SuspendLayout(); + fieldsTable.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)quantityUpDown).BeginInit(); + contentPanel.SuspendLayout(); + buttonPanel.SuspendLayout(); + SuspendLayout(); + // + // splitContainer + // + splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + splitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + splitContainer.Location = new System.Drawing.Point(0, 0); + splitContainer.Name = "splitContainer"; + // + // splitContainer.Panel1 + // + splitContainer.Panel1.Controls.Add(shapeListBox); + // + // splitContainer.Panel2 + // + splitContainer.Panel2.Controls.Add(layoutTable); + splitContainer.Size = new System.Drawing.Size(750, 520); + splitContainer.SplitterDistance = 150; + splitContainer.TabIndex = 0; + // + // shapeListBox + // + shapeListBox.BorderStyle = System.Windows.Forms.BorderStyle.None; + shapeListBox.Dock = System.Windows.Forms.DockStyle.Fill; + shapeListBox.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + shapeListBox.Font = new System.Drawing.Font("Segoe UI", 10F); + shapeListBox.IntegralHeight = false; + shapeListBox.ItemHeight = 32; + shapeListBox.Location = new System.Drawing.Point(0, 0); + shapeListBox.Name = "shapeListBox"; + shapeListBox.Size = new System.Drawing.Size(150, 520); + shapeListBox.TabIndex = 0; + // + // layoutTable + // + layoutTable.ColumnCount = 1; + layoutTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + layoutTable.Controls.Add(fieldsTable, 0, 0); + layoutTable.Controls.Add(contentPanel, 0, 1); + layoutTable.Controls.Add(buttonPanel, 0, 2); + layoutTable.Dock = System.Windows.Forms.DockStyle.Fill; + layoutTable.Location = new System.Drawing.Point(0, 0); + layoutTable.Name = "layoutTable"; + layoutTable.Padding = new System.Windows.Forms.Padding(6, 4, 6, 0); + layoutTable.RowCount = 3; + layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + layoutTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 44F)); + layoutTable.Size = new System.Drawing.Size(596, 520); + layoutTable.TabIndex = 0; + // + // fieldsTable + // + fieldsTable.AutoSize = true; + fieldsTable.ColumnCount = 2; + fieldsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + fieldsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + fieldsTable.Controls.Add(nameLabel, 0, 0); + fieldsTable.Controls.Add(nameTextBox, 1, 0); + fieldsTable.Controls.Add(qtyLabel, 0, 1); + fieldsTable.Controls.Add(quantityUpDown, 1, 1); + fieldsTable.Controls.Add(configLabel, 0, 2); + fieldsTable.Controls.Add(configComboBox, 1, 2); + fieldsTable.Dock = System.Windows.Forms.DockStyle.Fill; + fieldsTable.Location = new System.Drawing.Point(6, 4); + fieldsTable.Margin = new System.Windows.Forms.Padding(0, 0, 0, 4); + fieldsTable.Name = "fieldsTable"; + fieldsTable.RowCount = 3; + fieldsTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + fieldsTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + fieldsTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + fieldsTable.Size = new System.Drawing.Size(584, 97); + fieldsTable.TabIndex = 0; + // + // nameLabel + // + nameLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + nameLabel.AutoSize = true; + nameLabel.Location = new System.Drawing.Point(4, 8); + nameLabel.Margin = new System.Windows.Forms.Padding(4, 4, 8, 4); + nameLabel.Name = "nameLabel"; + nameLabel.Size = new System.Drawing.Size(46, 17); + nameLabel.TabIndex = 0; + nameLabel.Text = "Name:"; + // + // nameTextBox + // + nameTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + nameTextBox.Location = new System.Drawing.Point(102, 4); + nameTextBox.Margin = new System.Windows.Forms.Padding(0, 4, 4, 4); + nameTextBox.Name = "nameTextBox"; + nameTextBox.Size = new System.Drawing.Size(478, 25); + nameTextBox.TabIndex = 1; + // + // qtyLabel + // + qtyLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + qtyLabel.AutoSize = true; + qtyLabel.Location = new System.Drawing.Point(4, 41); + qtyLabel.Margin = new System.Windows.Forms.Padding(4, 4, 8, 4); + qtyLabel.Name = "qtyLabel"; + qtyLabel.Size = new System.Drawing.Size(59, 17); + qtyLabel.TabIndex = 2; + qtyLabel.Text = "Quantity:"; + // + // quantityUpDown + // + quantityUpDown.Location = new System.Drawing.Point(102, 37); + quantityUpDown.Margin = new System.Windows.Forms.Padding(0, 4, 4, 4); + quantityUpDown.Maximum = new decimal(new int[] { 999999, 0, 0, 0 }); + quantityUpDown.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + quantityUpDown.Name = "quantityUpDown"; + quantityUpDown.Size = new System.Drawing.Size(100, 25); + quantityUpDown.Suffix = ""; + quantityUpDown.TabIndex = 2; + quantityUpDown.Value = new decimal(new int[] { 1, 0, 0, 0 }); + // + // configLabel + // + configLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + configLabel.AutoSize = true; + configLabel.Location = new System.Drawing.Point(4, 73); + configLabel.Margin = new System.Windows.Forms.Padding(4, 4, 8, 4); + configLabel.Name = "configLabel"; + configLabel.Size = new System.Drawing.Size(90, 17); + configLabel.TabIndex = 3; + configLabel.Text = "Configuration:"; + configLabel.Visible = false; + // + // configComboBox + // + configComboBox.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + configComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + configComboBox.Location = new System.Drawing.Point(102, 70); + configComboBox.Margin = new System.Windows.Forms.Padding(0, 4, 4, 4); + configComboBox.Name = "configComboBox"; + configComboBox.Size = new System.Drawing.Size(478, 25); + configComboBox.TabIndex = 3; + configComboBox.Visible = false; + // + // contentPanel + // + contentPanel.Controls.Add(previewBox); + contentPanel.Controls.Add(parametersPanel); + contentPanel.Dock = System.Windows.Forms.DockStyle.Fill; + contentPanel.Location = new System.Drawing.Point(9, 108); + contentPanel.Name = "contentPanel"; + contentPanel.Size = new System.Drawing.Size(578, 365); + contentPanel.TabIndex = 1; + // + // previewBox + // + previewBox.ActiveWorkArea = null; + previewBox.AllowPan = false; + previewBox.AllowSelect = false; + previewBox.AllowZoom = false; + previewBox.BackColor = System.Drawing.Color.White; + colorScheme1.BackgroundColor = System.Drawing.Color.DarkGray; + colorScheme1.BoundingBoxColor = System.Drawing.Color.FromArgb(128, 128, 255); + colorScheme1.EdgeSpacingColor = System.Drawing.Color.FromArgb(180, 180, 180); + colorScheme1.LayoutFillColor = System.Drawing.Color.WhiteSmoke; + colorScheme1.LayoutOutlineColor = System.Drawing.Color.Gray; + colorScheme1.OriginColor = System.Drawing.Color.Gray; + colorScheme1.PreviewPartColor = System.Drawing.Color.FromArgb(255, 140, 0); + colorScheme1.RapidColor = System.Drawing.Color.DodgerBlue; + previewBox.ColorScheme = colorScheme1; + cutOffSettings1.CutDirection = CutDirection.AwayFromOrigin; + cutOffSettings1.MinSegmentLength = 0.05D; + cutOffSettings1.Overtravel = 0D; + cutOffSettings1.PartClearance = 0.02D; + previewBox.CutOffSettings = cutOffSettings1; + previewBox.DebugRemnantPriorities = null; + previewBox.DebugRemnants = null; + previewBox.Dock = System.Windows.Forms.DockStyle.Fill; + previewBox.DrawBounds = false; + previewBox.DrawCutDirection = false; + previewBox.DrawOffset = false; + previewBox.DrawOrigin = false; + previewBox.DrawPiercePoints = false; + previewBox.DrawRapid = false; + previewBox.FillParts = true; + previewBox.Location = new System.Drawing.Point(0, 0); + previewBox.Name = "previewBox"; + previewBox.OffsetIncrementDistance = 10D; + previewBox.OffsetTolerance = 0.001D; + plate1.CutOffs = observableList_11; + plate1.CuttingParameters = null; + plate1.GrainAngle = 0D; + plate1.Parts = observableList_12; + plate1.PartSpacing = 0D; + plate1.Quadrant = 1; + plate1.Quantity = 0; + previewBox.Plate = plate1; + previewBox.RotateIncrementAngle = 10D; + previewBox.ShowBendLines = false; + previewBox.Size = new System.Drawing.Size(318, 365); + previewBox.Status = "Select"; + previewBox.TabIndex = 4; + previewBox.TabStop = false; + // + // parametersPanel + // + parametersPanel.AutoScroll = true; + parametersPanel.Dock = System.Windows.Forms.DockStyle.Right; + parametersPanel.Location = new System.Drawing.Point(318, 0); + parametersPanel.Name = "parametersPanel"; + parametersPanel.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0); + parametersPanel.Size = new System.Drawing.Size(260, 365); + parametersPanel.TabIndex = 5; + // + // buttonPanel + // + buttonPanel.Controls.Add(addButton); + buttonPanel.Controls.Add(closeButton); + buttonPanel.Dock = System.Windows.Forms.DockStyle.Fill; + buttonPanel.Location = new System.Drawing.Point(9, 479); + buttonPanel.Name = "buttonPanel"; + buttonPanel.Size = new System.Drawing.Size(578, 38); + buttonPanel.TabIndex = 2; + // + // addButton + // + addButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + addButton.Location = new System.Drawing.Point(379, 5); + addButton.Name = "addButton"; + addButton.Size = new System.Drawing.Size(100, 30); + addButton.TabIndex = 0; + addButton.Text = "Add to Nest"; + addButton.UseVisualStyleBackColor = true; + // + // closeButton + // + closeButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + closeButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + closeButton.Location = new System.Drawing.Point(485, 5); + closeButton.Name = "closeButton"; + closeButton.Size = new System.Drawing.Size(90, 30); + closeButton.TabIndex = 1; + closeButton.Text = "Close"; + closeButton.UseVisualStyleBackColor = true; + // + // ShapeLibraryForm + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + CancelButton = closeButton; + ClientSize = new System.Drawing.Size(750, 520); + Controls.Add(splitContainer); + Font = new System.Drawing.Font("Segoe UI", 9.75F); + MinimizeBox = false; + MinimumSize = new System.Drawing.Size(600, 400); + Name = "ShapeLibraryForm"; + ShowIcon = false; + ShowInTaskbar = false; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Shape Library"; + splitContainer.Panel1.ResumeLayout(false); + splitContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer).EndInit(); + splitContainer.ResumeLayout(false); + layoutTable.ResumeLayout(false); + layoutTable.PerformLayout(); + fieldsTable.ResumeLayout(false); + fieldsTable.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)quantityUpDown).EndInit(); + contentPanel.ResumeLayout(false); + buttonPanel.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer; + private System.Windows.Forms.ListBox shapeListBox; + private System.Windows.Forms.TableLayoutPanel layoutTable; + private System.Windows.Forms.TableLayoutPanel fieldsTable; + private System.Windows.Forms.Label nameLabel; + private System.Windows.Forms.TextBox nameTextBox; + private System.Windows.Forms.Label qtyLabel; + private Controls.NumericUpDown quantityUpDown; + private System.Windows.Forms.Label configLabel; + private System.Windows.Forms.ComboBox configComboBox; + private System.Windows.Forms.Panel contentPanel; + private Controls.ShapePreviewControl previewBox; + private System.Windows.Forms.Panel parametersPanel; + private System.Windows.Forms.Panel buttonPanel; + private System.Windows.Forms.Button addButton; + private System.Windows.Forms.Button closeButton; + } +} diff --git a/OpenNest/Forms/ShapeLibraryForm.cs b/OpenNest/Forms/ShapeLibraryForm.cs new file mode 100644 index 0000000..0b98dec --- /dev/null +++ b/OpenNest/Forms/ShapeLibraryForm.cs @@ -0,0 +1,322 @@ +using OpenNest.Shapes; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace OpenNest.Forms +{ + public partial class ShapeLibraryForm : Form + { + private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + private readonly List addedDrawings = new List(); + private readonly List shapeEntries = new List(); + private readonly List parameterBindings = new List(); + + private ShapeEntry selectedEntry; + private bool suppressPreview; + + public ShapeLibraryForm() + { + InitializeComponent(); + DiscoverShapes(); + PopulateShapeList(); + + shapeListBox.DrawItem += ShapeListBox_DrawItem; + shapeListBox.SelectedIndexChanged += ShapeListBox_SelectedIndexChanged; + configComboBox.SelectedIndexChanged += ConfigComboBox_SelectedIndexChanged; + addButton.Click += AddButton_Click; + closeButton.Click += (s, e) => Close(); + + if (shapeListBox.Items.Count > 0) + shapeListBox.SelectedIndex = 0; + } + + public List GetDrawings() => addedDrawings; + + private void DiscoverShapes() + { + var baseType = typeof(ShapeDefinition); + var shapeTypes = baseType.Assembly.GetTypes() + .Where(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t)) + .OrderBy(t => t.Name) + .ToList(); + + var configDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configurations"); + + foreach (var type in shapeTypes) + { + var entry = new ShapeEntry { ShapeType = type }; + entry.DisplayName = FriendlyName(type.Name); + + var configPath = Path.Combine(configDir, type.Name + ".json"); + if (File.Exists(configPath)) + entry.Configurations = LoadConfigurations(type, configPath); + + shapeEntries.Add(entry); + } + } + + private List LoadConfigurations(Type shapeType, string path) + { + try + { + var json = File.ReadAllText(path); + var listType = typeof(List<>).MakeGenericType(shapeType); + var list = JsonSerializer.Deserialize(json, listType, JsonOptions); + return ((System.Collections.IEnumerable)list).Cast().ToList(); + } + catch + { + return null; + } + } + + private void PopulateShapeList() + { + foreach (var entry in shapeEntries) + shapeListBox.Items.Add(entry); + } + + private void ShapeListBox_DrawItem(object sender, DrawItemEventArgs e) + { + if (e.Index < 0) return; + + e.DrawBackground(); + + var entry = (ShapeEntry)shapeListBox.Items[e.Index]; + var textColor = (e.State & DrawItemState.Selected) != 0 + ? SystemColors.HighlightText + : SystemColors.ControlText; + + var text = entry.DisplayName; + if (entry.HasConfigurations) + text += $" ({entry.Configurations.Count})"; + + using (var brush = new SolidBrush(textColor)) + { + var format = new StringFormat { LineAlignment = StringAlignment.Center }; + var rect = new RectangleF(8, e.Bounds.Y, e.Bounds.Width - 8, e.Bounds.Height); + e.Graphics.DrawString(text, e.Font, brush, rect, format); + } + + e.DrawFocusRectangle(); + } + + private void ShapeListBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (shapeListBox.SelectedIndex < 0) return; + + selectedEntry = (ShapeEntry)shapeListBox.SelectedItem; + suppressPreview = true; + + var hasConfigs = selectedEntry.HasConfigurations; + configLabel.Visible = hasConfigs; + configComboBox.Visible = hasConfigs; + + if (hasConfigs) + { + configComboBox.Items.Clear(); + foreach (var cfg in selectedEntry.Configurations) + configComboBox.Items.Add(cfg.Name); + + configComboBox.SelectedIndex = 0; + } + else + { + nameTextBox.Text = selectedEntry.DisplayName; + var defaults = (ShapeDefinition)Activator.CreateInstance(selectedEntry.ShapeType); + defaults.SetPreviewDefaults(); + BuildParameterControls(selectedEntry.ShapeType, defaults); + } + + suppressPreview = false; + UpdatePreview(); + } + + private void ConfigComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (configComboBox.SelectedIndex < 0 || selectedEntry == null) return; + + var config = selectedEntry.Configurations[configComboBox.SelectedIndex]; + nameTextBox.Text = config.Name; + + suppressPreview = true; + BuildParameterControls(selectedEntry.ShapeType, config); + suppressPreview = false; + UpdatePreview(); + } + + private void BuildParameterControls(Type shapeType, ShapeDefinition sourceValues) + { + parametersPanel.SuspendLayout(); + parametersPanel.Controls.Clear(); + parameterBindings.Clear(); + + var props = shapeType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(p => p.CanRead && p.CanWrite && p.Name != "Name") + .ToArray(); + + var panelWidth = parametersPanel.ClientSize.Width - parametersPanel.Padding.Horizontal; + var y = 4; + + foreach (var prop in props) + { + var label = new Label + { + Text = FriendlyName(prop.Name), + Location = new Point(parametersPanel.Padding.Left, y), + AutoSize = true + }; + + y += 18; + + var tb = new TextBox + { + Location = new Point(parametersPanel.Padding.Left, y), + Width = panelWidth, + Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right + }; + + if (sourceValues != null) + { + if (prop.PropertyType == typeof(int)) + tb.Text = ((int)prop.GetValue(sourceValues)).ToString(); + else + tb.Text = ((double)prop.GetValue(sourceValues)).ToString("G"); + } + + tb.TextChanged += (s, ev) => UpdatePreview(); + + parameterBindings.Add(new ParameterBinding { Property = prop, Control = tb }); + + parametersPanel.Controls.Add(label); + parametersPanel.Controls.Add(tb); + + y += 30; + } + + parametersPanel.ResumeLayout(true); + } + + private void UpdatePreview() + { + if (suppressPreview || selectedEntry == null) return; + + try + { + var shape = CreateShapeFromInputs(); + if (shape == null) return; + + var drawing = shape.GetDrawing(); + previewBox.ShowDrawing(drawing); + + if (drawing?.Program != null) + { + var bb = drawing.Program.BoundingBox(); + previewBox.SetInfo( + nameTextBox.Text, + string.Format("{0:F3} x {1:F3}", bb.Size.Length, bb.Size.Width)); + } + } + catch + { + previewBox.ShowDrawing(null); + } + } + + private ShapeDefinition CreateShapeFromInputs() + { + var shape = (ShapeDefinition)Activator.CreateInstance(selectedEntry.ShapeType); + shape.Name = nameTextBox.Text; + + foreach (var binding in parameterBindings) + { + var tb = (TextBox)binding.Control; + + if (binding.Property.PropertyType == typeof(int)) + { + if (int.TryParse(tb.Text, out var intVal)) + { + binding.Property.SetValue(shape, intVal); + tb.ForeColor = SystemColors.WindowText; + } + else + { + tb.ForeColor = Color.Red; + return null; + } + } + else + { + var val = ArchUnits.GetLengthInches(tb); + if (double.IsNaN(val)) + return null; + + binding.Property.SetValue(shape, val); + } + } + + return shape; + } + + private void AddButton_Click(object sender, EventArgs e) + { + try + { + var shape = CreateShapeFromInputs(); + if (shape == null) return; + + var drawing = shape.GetDrawing(); + drawing.Color = Drawing.GetNextColor(); + drawing.Quantity.Required = (int)quantityUpDown.Value; + + addedDrawings.Add(drawing); + DialogResult = DialogResult.OK; + + addButton.Text = $"Added ({addedDrawings.Count})"; + } + catch (Exception ex) + { + MessageBox.Show( + $"Failed to create shape: {ex.Message}", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + } + } + + private static string FriendlyName(string name) + { + if (name.EndsWith("Shape")) + name = name.Substring(0, name.Length - 5); + + return Regex.Replace(name, @"(?<=[a-z0-9])([A-Z])", " $1"); + } + + private class ShapeEntry + { + public Type ShapeType { get; set; } + public string DisplayName { get; set; } + public List Configurations { get; set; } + public bool HasConfigurations => Configurations != null && Configurations.Count > 0; + + public override string ToString() => DisplayName; + } + + private class ParameterBinding + { + public PropertyInfo Property { get; set; } + public Control Control { get; set; } + } + } +} diff --git a/OpenNest/Forms/ShapeLibraryForm.resx b/OpenNest/Forms/ShapeLibraryForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/OpenNest/Forms/ShapeLibraryForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/OpenNest/OpenNest.csproj b/OpenNest/OpenNest.csproj index 3f0613d..8920884 100644 --- a/OpenNest/OpenNest.csproj +++ b/OpenNest/OpenNest.csproj @@ -10,6 +10,11 @@ + + + PreserveNewest + +